Reputation: 302
I have the following code fragment:
@UseExperimental(ExperimentalCoroutinesApi::class)
fun main() {
fun CoroutineScope.send(id: Int) {
if (id % 11 == 0) cancel()
if (id % 5 == 0) throw IllegalArgumentException()
println(id)
}
fun send() = runBlocking {
val handler = CoroutineExceptionHandler { _, _ ->
println("Something bad happened")
}
val jobs = (1..1000).map {
it to launch(handler) {
send(it)
}
}.toMap()
jobs.values.joinAll()
println("Unable to send message to ids: ${jobs.filterValues { it.isCancelled }.keys.joinToString()}")
}
send()
}
When I run this code I get the following result:
1
2
3
4
Exception in thread "main" java.lang.IllegalArgumentException
I have a couple of questions:
Why the CoroutineExceptionHandler
doesn't handle the exception and why it's propagated instead?
If I comment out the line if (id % 5 == 0) throw IllegalArgumentException()
I still can see the printed ids 11, 22, 33 etc. in the console. It seems that cancel()
doesn't interrupt the coroutine straight away. Is this the case?
Upvotes: 0
Views: 1902
Reputation: 200158
Why the
CoroutineExceptionHandler
doesn't handle the exception and why it's propagated instead?
That is its contract. The name "handler" is actually a misnomer because it doesn't handle the exception, it only observes it, and it does so only at the point where the exception is about to get lost, having already escaped from the top-level coroutine. You have installed the handler into child coroutines, where it has no effect. This is actually specified at the very beginning of its documentation:
interface CoroutineExceptionHandler : Element (source)
An optional element in the coroutine context to handle uncaught exceptions.
Normally, uncaught exceptions can only result from root coroutines created using the launch builder. All children coroutines (coroutines created in the context of another Job) delegate handling of their exceptions to their parent coroutine, which also delegates to the parent, and so on until the root, so the CoroutineExceptionHandler installed in their context is never used.
If I comment out the line
if (id % 5 == 0) throw IllegalArgumentException()
I still can see the printed ids 11, 22, 33 etc. in the console. It seems thatcancel()
doesn't interrupt the coroutine straight away. Is this the case?
Cancellation is a cooperative mechanism. The only immediate effect of cancel()
is to lower the coroutine's isActive
flag. The runtime has the opportunity to read the flag only within a suspension point, of which you have none (send
is not even a suspend fun
).
Upvotes: 4