Reputation: 661
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.
Upvotes: 37
Views: 43362
Reputation: 422
In my case, the issue was caused due to an inner part of the code using non-reactive code which was causing the upper-level dispatched coroutines to be canceled. At the end of the day, I fixed the problem by using the necessary reactive code in the underlying layer but, I was able to catch it by switching from:
withContext(Dispatchers.Default) {
// dispatched coroutine
}
to:
withContext(NonCancellable) {
// dispatched coroutine
}
This stopped "hiding" the problem and pointed me right to the root of the of it.
Hope this helps =)
Upvotes: 5
Reputation: 28809
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
.
Upvotes: 1
Reputation: 2896
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
Upvotes: 0
Reputation: 93
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)
}
Upvotes: 0
Reputation: 169
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
Upvotes: 10
Reputation: 113
I just saw the same problem. The issue was caused by the activity being finished manually before the job managed to complete.
Upvotes: 6
Reputation: 661
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.
Upvotes: 18