It seems like `CoroutineExceptionHandler`s are ign...
# coroutines
z
It seems like `CoroutineExceptionHandler`s are ignored for exceptions thrown from a coroutine `launch`ed with a parent
Job
from a
runBlocking
scope. Changing the dispatcher doesn’t seem to matter, and coroutines `launch`ed from
GlobalScope
execute the handler as I’d expect. I can’t see any documentation about this or figure out how this makes sense. E.g.
Copy code
fun main(args: Array<String>) {
  val handler = CoroutineExceptionHandler { _, exception ->
    println("Caught $exception")
  }

  runBlocking(handler) {
    val job = launch {
      // This exception is thrown from runBlocking, and not seen by handler.
      throw AssertionError()
    }
    job.join()
  }
}
If you change that to the following,
handler
gets run:
Copy code
runBlocking {
  val job = GlobalScope.launch(handler) {
Is this behavior by design?
Ah, I think I get it – the existence of a
runBlocking
parent job implies there is a well-defined place in the code where the exception can be caught, unlike a
GlobalScope.launch
.
d
Yeah, I think it's because there are 2 context elements with the same key. I don't know how they are prioritized. I guess this example would suggest the parent context is prioritized, but in the case of the intercepter it should not.
The continuation intercepter does get special treatment with regards to internal ordering for performance, which might have something to do with it.
In short:
CoroutineExceptionHandler
is a handler for uncaught exceptions, while child coroutines effectively have their exceptions “caught” by their parent
👍 1
Note that
GlobalScope.launch
completely decouples coroutine from its parent. If you want to inherit parent’s dispatcher, but make it separate in terms of cancellation and exception you can do
launch(NonCancellable)
👍 1
d
Good read!
z
Thanks!
s
Any particular reason it is not named
CoroutineUncaughtExceptionHandler
then ?
e
It could have been, but that is quite a long name