How to fix Kotlin JobCancellationException? - android

I got a crash because of Kotlin JobCancellationException.
The following is the detail about the crash :
kotlinx.coroutines.JobCancellationException: Job was cancelled; job=SupervisorJobImpl{Cancelling}#131dbe3
All I know is the SupervisorJobImpl is for ViewModelScope, and it will be called method cancel when ViewModel lifecycle is over.
I was so confused about the Exception because Kotlin coroutines will just ignore the Exception, but it was thrown and cause the App crash. If it has stack I can just figure out, but it doesn't, just tell me that the job was cancelled.
I spent about more than 3 days on the exception but just have no idea.
I saw the video :
KotlinConf 2019: Coroutines! Gotta catch 'em all! by Florina Muntenescu & Manuel Vivo, I found if the scope is canceled, and if you call await on a Deferred, it will throw the Exception, but I found no await on the canceled scope.
So can someone just show me some code which perhaps causes the same exception and make the App crash? Thx, there.

Finally, I found what causes the Exception and the issue address is flowing:
kotlin.coroutines.channels.awaitClose: JobCancellationException
Actually, awaitClose will not throw the JobCancellationException, because awaitClose is a cancellable suspended function. The offer method will throw JobCancellationException if the Job was canceled because offer is not a cancellable suspended function.
By the way, callbackFlow is an experimental API, so it may cause some bug, so when we use it, we need to be careful. Because it will not always ignore JobCancellationException when Job was canceled, and I don't think it's friendly to developers.
Now I have found 2 situations that will cause JobCancellationException so we need to try catch the exception.
async await, when we call the await method we need to try catch. And you can find and example in the Video.
callbackFlow offer, when we call the offer method we need to try catch. And you can find an example in the issue above.

I know I am late but you can just check Job Status before offering objects.
Like this
if(isActive) offer(Resource.success(response))
isActive is Coroutine Scope

I just saw the same problem. The issue was caused by the activity being finished manually before the job managed to complete.

I closed a ViewModel and a dialog and then started a Job. It led to this exception and HTTP-request cancellation: HTTP FAILED: java.io.IOException: Canceled.
close()
modelScope.launch {
val response = withContext(Dispatchers.IO) {
...
}
response?.let { ... }
}
I simply moved close() to the end.
When you call one coroutine from another, parent coroutine will cancel nested, if finish quicker. For instance:
viewModelScope.launch {
// Launch a request.
...
// Now launch a nested coroutine.
viewModelScope.launch { // (1)
... // Can be cancelled. (2)
}
}
You can remove viewModelScope.launch { in (1) and rewrite a nested block as suspended. If you want a parallel work, use async/await or this:
viewModelScope.launch {
...
}
// A parallel coroutine.
viewModelScope.launch {
...
}
There is a case when everything works well, but once in 100 clicks a coroutine cancels itself (the same "HTTP FAILED: java.io.IOException: Canceled"). Nothing helped, until I changed lifecycleScope.launch to lifecycleScope.launchWhenResumed.

class MyApp: Application() {
override fun onCreate() {
super.onCreate()
System.setProperty(DEBUG_PROPERTY_NAME, DEBUG_PROPERTY_VALUE_ON)
}
in this way you will be able to pinpoint the main cause of the issue an deal with it

I have the same bug as you. So I try to write simple flow extensions like below:
fun <P> Flow<DataResult<P>>.safeStart(start: suspend FlowCollector<DataResult<P>>.() -> Unit)
: Flow<DataResult<P>> = onStart {
if (!currentCoroutineContext().isActive) return#onStart
start()
}
fun <P> Flow<DataResult<P>>.safeCatch(onCatch: suspend FlowCollector<DataResult<P>>.(cause: Throwable) -> Unit)
: Flow<DataResult<P>> = catch {
if (!currentCoroutineContext().isActive) return#catch
onCatch(it)
}
suspend inline fun <P> Flow<DataResult<P>>.safeCollect(crossinline onCollect: suspend (value: DataResult<P>) -> Unit)
: Unit = collect {
if (!currentCoroutineContext().isActive) return#collect
onCollect(it)
}

Had the same crash when I used callbackFlow with offer with coroutines version 1.3.5.
Now instead of offer I use trySend and it's fixed.
Note:
trySend method is available (and offer is deprecated) when you update coroutines version to:
org.jetbrains.kotlinx:kotlinx-coroutines-core:1.4.0

Related

CoroutineScope cancelation

I exactly understand how are suspendCoroutine vs suspendCancellableCoroutine work in my samples. But im wondering why println("I finished") (line 13 - second line in viewscope block) executed after i had called viewScope.cancel(). I can fix it with isActive flag before this line but i don't want to check each line. What am i missing there. How i can cancel scope as well ? Thanks
import kotlinx.coroutines.*
import java.lang.Exception
import kotlin.coroutines.CoroutineContext
import kotlin.coroutines.resume
import kotlin.coroutines.suspendCoroutine
fun main() {
val parentJob = Job()
val viewScope = CoroutineScope(Dispatchers.IO + parentJob)
viewScope.launch {
println(tryMe())
println("I finished")
}
Thread.sleep(2000)
viewScope.cancel()
Thread.sleep(10000)
}
suspend fun tryMe() = suspendCoroutine<String> {
println("I started working")
Thread.sleep(6000)
println("Im still working :O")
it.resume("I returned object at the end :)")
}
suspend fun tryMe2() = suspendCancellableCoroutine<String> {
println("I started working")
Thread.sleep(6000)
println("Im still working :O")
it.resume("I returned object at the end :)")
}
suspend fun tryMe3() = suspendCancellableCoroutine<String> {
it.invokeOnCancellation { println("I canceled did you heard that ?") }
println("I started working")
Thread.sleep(6000)
if (it.isActive)
println("Im still working :O")
it.resume("I returned object at the end :)")
}
If we just call cancel, it doesn’t mean that the coroutine work will just stop. If you’re performing some relatively heavy computation, like reading from multiple files, there’s nothing that automatically stops your code from running. Once job.cancel is called, our coroutine moves to Cancelling state.
Cancellation of coroutine code needs to be coperative
You need to make sure that all the coroutine work you’re implementing is cooperative with cancellation, therefore you need to check for cancellation periodically or before beginning any long running work. For example, if you’re reading multiple files from disk, before you start reading each file, check whether the coroutine was cancelled or not. Like this you avoid doing CPU intensive work when it’s not needed anymore.
All suspend functions from kotlinx.coroutines are cancellable: withContext, delay etc. So if you’re using any of them you don’t need to check for cancellation and stop execution or throw a CancellationException. But, if you’re not using them, to make your coroutine code cooperative by checking job.isActive or ensureActive()
Coroutine cancellation is co-operative
You should check whether the coroutine is still active before println("I finished") if you wish that statement not to be executed if the coroutine is canceled, like it follows:
if (isActive)
println("I finished")
Why is that?
Coroutines are not guranteed to be dispatched on another thread. So, while threads provide means to be aborted, which is implemented either at system–level or user–level in the runtime (e.g. the JVM, or ART), coroutines that are not backed by a thread couldn't be canceled anyhow, because the only thing that could be done is throw an exception, but that would abort the whole current execution context (i.e. thread), where other coroutines may be running.
Other answers talk about heavy computations, but that's plainly wrong. No matter what you're doing, whether computationally heavy or not — coroutines can't be forcefully canceled; their cancellation is only a request to be canceled, it's up to the coroutine body to handle cancellation requests using the CoroutineScope property isActive, which gets a boolean indicating whether the work the coroutine is carrying on should continue, if true; or be canceled, if false.
Using suspendCancellableCoroutine is working as expected. suspendCoroutine doesn't check for the cancellation state of the coroutineScope(internally uses safeContinuation) while suspendCancellableCoroutine does checks for the cancellation via CancellableContinuationImpl and it cancels the resume operation.

Coroutine keeps crashing without showing error

I'm developing in MVP. In my Presenter, I call my Repository thanks to the suspend function. In this suspend function, I launch a Coroutine - not on the main thread.
When this coroutine is finished, I want to execute some code: I am using withContext() in order to do so.
In my Repository, I am launching a Coroutine (and maybe I am wrong) to insert my data, using DAO, in my Room database.
When I debug my application, it goes into my Presenter but crashes before going into my Repository.
Presenter
override suspend fun insertUserResponse(activity: Activity, data: UserResponse) {
scope.launch(Dispatchers.IO) {
try {
userResponseRepository.insertUserResponse(data)
withContext(Dispatchers.Main) {
redirectToClientMainPage(activity)
}
} catch (error: Exception) {
parentJob.cancel()
Log.e("ERROR PRESENTER", "${error.message}")
}
}
}
Repository
override suspend fun insertUserResponse(userResponse: UserResponse) {
GlobalScope.launch(Dispatchers.IO) {
try {
val existingUser: UserResponse? =
userResponseDAO.searchUserByID(userResponse.profilePOJO.uniqueID)
existingUser?.let {
userResponseDAO.updateUser(userResponse)
} ?: userResponseDAO.insertUser(userResponse)
} catch (error: Exception) {
Log.e("ERROR REPOSITORY", "${error.message}")
}
}
}
I have no error shown in my logcat.
EDIT:
Scope initialization
private var parentJob: Job = Job()
override val coroutineContext: CoroutineContext
get() = uiContext + parentJob
private val scope = CoroutineScope(coroutineContext)
val uiContext: CoroutineContext = Dispatchers.Main (initialized in my class constructor)
Stack trace
I finally found the answer!
Thanks to #sergiy I read part II of this article https://medium.com/androiddevelopers/coroutines-on-android-part-ii-getting-started-3bff117176dd and it mentions that you can't catch error except Throwable and CancellationException.
So instead of catching Exception, I traded it for Throwable. And finally I had an error shown in my logcat.
I am using Koin to inject my repository and all. I was missing my androidContext() in my Koin application.
That's it.
Without stacktrace it's hard to help you.
Here is an article, that might be helpful. Replacing ViewModel mentioned in the article with Presenter in your case you can get several general recommendations in using coroutines:
As a general pattern, start coroutines in the ViewModel (Read: Presenter)
I don't see the need to launch one more coroutine in your case in Repository.
As for switching coroutine's context for Room:
Room uses its own dispatcher to run queries on a background thread. Your code should not use withContext(Dispatchers.IO) to call suspending room queries. It will complicate the code and make your queries run slower.
I couldn't find the same recommendation in official docs, but it's mentioned in one of the Google code labs.
Both Room and Retrofit make suspending functions main-safe.
It's safe to call these suspend funs from Dispatchers.Main, even though they fetch from the network and write to the database.
So you can omit launch (as well as withContext) in Repository, since Room guarantees that all methods with suspend are main-safe. That means that they can be called even from the main thread. Also you can not to define Dispatchers.IO explicitly in Presenter.
One more thing. If Presenter's method insertUserResponse is suspend, then you call it from another launched coroutine, don't you? In that case, why you launch one more coroutine inside this method? Or maybe this method shouldn't be suspend?

Android kotlin coroutine: await might silently drop exceptions

While reading https://developer.android.com/kotlin/coroutines I stumbled upon the following warning:
Warning: launch and async handle exceptions differently. Since async expects an eventual call to await at some point, it holds exceptions and rethrows them as part of the await call. This means if you use await to start a new coroutine from a regular function, you might silently drop an exception. These dropped exceptions won't appear in your crash metrics or be noted in logcat.
However I'm not able to find any example of this silent-dropping behavior while browsing https://kotlinlang.org/docs/reference/coroutines/exception-handling.html or any other resources returned by https://www.google.com/search?q=kotlin+await+exception+handling - on the contrary, all resources indicate that exception thrown in an async/await block will cause a failure in the whole coroutine scope which is correct and expected.
I'm afraid I'm missing something here, can you provide an example where this silent exception dropping occurs which cannot be noted in logcat?
The passage you quote from Kotlin documentation is outdated. It used to be true in the experimental phase, and you really could get swallow exceptions if you weren't very pedantic.
The release version of coroutines acquired an additional key component: structured concurrency. When following the simple guidelines not to use GlobalScope and similar, your code will automatically be organized such that each coroutine has a parent and there's a well-defined scope within which all coroutines must complete either normally or abruptly, and the owner of that scope can await its completion, getting the exception that caused the abnormal completion.
Within this new discipline, launch and async are on the same footing. If an exception thrown inside one of them escapes its top-level block, it goes on to cancel the coroutine, notify the parent of this, and then the parent may opt to cancel all the other children so that the whole scope ends prematurely.
Note that getting an exception from the await call is an entirely different mechanism. The thing you await on is not the async block itself, but a standalone object of type Deferred, which is identical to Java's Future except that await suspends instead of blocks. The async block, upon completion, always does the same: it completes the Deferred with either the return value or the exception. You may retrieve that result from anywhere you pass the Deferred, it's completely decoupled from the destiny of the async coroutine.
Ok, I found an example which is in line with Marko's answer concerning structured concurrency. Code snippet below presents the silent exception dropping usecase.
This example comes from https://medium.com/androiddevelopers/coroutines-on-android-part-ii-getting-started-3bff117176dd
val unrelatedScope = MainScope()
// example of a lost error
suspend fun lostError() {
// async without structured concurrency
unrelatedScope.async {
throw InAsyncNoOneCanHearYou("except")
}
}
Note this code is declaring an unrelated coroutine scope that will launch a new coroutine without structured concurrency. [...]
The error is lost in this code because async assumes that you will eventually call await where it will rethrow the exception. However, if you never do call await, the exception will be stored forever waiting patiently to be raised.
Exceptions from launch are thrown "instantly" into exception handler, while those from async will be thrown the moment you call await.
Sample code:
import kotlinx.coroutines.*
fun main(args: Array<String>) = runBlocking{
println("main start")
val l = GlobalScope.launch{ justThrow("launch") }
val a = GlobalScope.async { justThrow("async") }
delay(500)
// a.await()
println("main finished")
}
suspend fun justThrow(who : String){
println("starting $who")
delay(100)
throw Exception("Test exception from $who")
}
Output:
main start
starting launch
starting async
Exception in thread "DefaultDispatcher-worker-2 #coroutine#2" java.lang.Exception: Test exception from launch
at FileKt.justThrow(File.kt:15)
(... entire stack here )
main finished
You can see launch throwing exception in worker thread but there's nothing from async, even though it executed the same function that must fail.
For next run modify the code by removing the launch for clarity and uncommenting a.await():
fun main(args: Array<String>) = runBlocking{
println("main start")
val a = GlobalScope.async { justThrow("async") }
delay(500)
a.await()
println("main finished")
}
Output:
main start
starting async
Exception in thread "main" java.lang.Exception: Test exception from async
at FileKt.justThrow (File.kt:15)
(... entire stack here )
Exception is held instead of crashing in worker thread and is instead re-thrown the moment await() is called which causes the app to crash. You can notice it happened on main thread and there was no "main finished" print because of that.

How Kotlin coroutines are scheduled

I've been reading a lot of articles and watching a lot of videos on Kotlin co-routines lately and, despite my efforts, I still can't make sense of them in my head.
I think I've finally found a way to illustrate my problem:
class MyViewModel() : CoroutineScope {
override val coroutineContext = Dispatchers.Main + Job()
fun foo() = launch(handler) {
Log.e("test", "A")
}
}
class MainActivity : Activity() {
override fun onCreate() {
MainViewModel().foo()
Log.e("test", "B")
}
}
The output of this is:
E/test: B
E/test: A
And I don't get how this can be the case, I'm using only one thread (the main thread). If my code executes sequentially, by the time I reach the line log(B)... log(A) should have already be printed.
Does the coroutines library use other threads internally to accomplish this? This is the only explanation I could come up with but haven't found anything saying so in the docs.
PS: Sorry for throwing android into the mix but this code:
fun main() {
GlobalScope.launch(Dispatchers.Unconfined) { // launch new coroutine in background and continue
print(Thread.currentThread().name + "World!") // print after delay
}
(0 .. 1000).forEach { print(".") }
}
seems to work as expected and prints:
main #coroutine#1World!...........................
because 1 thread == sequential work
Hope my question makes sense, thanks for reading!
Under the hood, the Main dispatcher uses a Handler to post a Runnable to the MessageQueue. Basically, it’ll get added to the end of the event queue. This means it will execute soon, but not immediately. Hence why “B” gets printed before “A”.
You can find more information in this article.
EDIT by OP (read the article above before reading this):
Just wanted to clarify why the android example above was working fine, in case someone is still wondering.
fun main() {
GlobalScope.launch(Dispatchers.Unconfined) { // launch new coroutine in background and continue
print(Thread.currentThread().name + "World!") // print after delay
}
(0 .. 1000).forEach { print(".") }
}
We're setting GlobalScope to use the UNCONFINED dispatcher, and this dispatcher has isDispatchNeeded set to false.
false means "schedule in the current thread", and that's why we see the logs printing sequentially. UNCONFINED should not be used in regular code.
All other dispatchers have isDispatchNeeded set to true even the UI dispatcher. see: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-dispatcher/is-dispatch-needed.html
(btw GlobalScope uses the Default dispatcher if we don't specify one)

Kotlin coroutines `runBlocking`

I am learning Kotlin coroutines. I've read that runBlocking is the way to bridge synchronous and asynchronous code. But what is the performance gain if the runBlocking stops the UI thread?
For example, I need to query a database in Android:
val result: Int
get() = runBlocking { queryDatabase().await() }
private fun queryDatabase(): Deferred<Int> {
return async {
var cursor: Cursor? = null
var queryResult: Int = 0
val sqlQuery = "SELECT COUNT(ID) FROM TABLE..."
try {
cursor = getHelper().readableDatabase.query(sqlQuery)
cursor?.moveToFirst()
queryResult = cursor?.getInt(0) ?: 0
} catch (e: Exception) {
Log.e(TAG, e.localizedMessage)
} finally {
cursor?.close()
}
return#async queryResult
}
}
Querying the database would stop the main thread, so it seems that it would take the same amount of time as synchronous code? Please correct me if I am missing something.
runBlocking is the way to bridge synchronous and asynchronous code
I keep bumping into this phrase and it's very misleading.
runBlocking is almost never a tool you use in production. It undoes the asynchronous, non-blocking nature of coroutines. You can use it if you happen to already have some coroutine-based code that you want to use in a context where coroutines provide no value: in blocking calls. One typical use is JUnit testing, where the test method must just sit and wait for the coroutine to complete.
You can also use it while playing around with coroutines, inside your main method.
The misuse of runBlocking has become so widespread that the Kotlin team actually tried to add a fail-fast check which would immediately crash your code if you call it on the UI thread. By the time they did this, it was already breaking so much code that they had to remove it.
Actually you use runBlocking to call suspending functions in "blocking" code that otherwise wouldn't be callable there or in other words: you use it to call suspend functions outside of the coroutine context (in your example the block passed to async is the suspend function). Also (more obvious, as the name itself implies already), the call then is a blocking call. So in your example it is executed as if there wasn't something like async in place. It waits (blocks interruptibly) until everything within the runBlocking-block is finished.
For example assume a function in your library as follows:
suspend fun demo() : Any = TODO()
This method would not be callable from, e.g. main. For such a case you use runBlocking then, e.g.:
fun main(args: Array<String>) {
// demo() // this alone wouldn't compile... Error:() Kotlin: Suspend function 'demo' should be called only from a coroutine or another suspend function
// whereas the following works as intended:
runBlocking {
demo()
} // it also waits until demo()-call is finished which wouldn't happen if you use launch
}
Regarding performance gain: actually your application may rather be more responsive instead of being more performant (sometimes also more performant, e.g. if you have multiple parallel actions instead of several sequential ones). In your example however you already block when you assign the variable, so I would say that your app doesn't get more responsive yet. You may rather want to call your query asynchronously and then update the UI as soon as the response is available. So you basically just omit runBlocking and rather use something like launch. You may also be interested in Guide to UI programming with coroutines.

Categories

Resources