Propagate exception from async to CoroutineExceptionHandler

Viewed 458

I have an async coroutine, that might throw an exception. How can I propagate the exception to my CoroutineExceptionHandler? With try/catch around await I'm able to catch the exception, but I can't seem to propagate the handling to the handler, no matter the context:

val handler = CoroutineExceptionHandler { _, e -> e.printStackTrace() }
val context = Executors.newSingleThreadExecutor().asCoroutineDispatcher() + handler
val scope = CoroutineScope(context)

val async = scope.async {
    throw Exception("Test exception")
}
runBlocking {
    try {
        async.await()
    } catch (e: Exception) {
        log.error("Exception thrown", e)
    }
    delay(1_000)
}

I've tried rethrowing the exception from the catch, or wrapping it with some of the following constructs:

catch (e: Exception) {
    throw e
    // withContext(context) { throw e }
    // scope.launch { throw e }
    // supervisorScope { throw e }
}

Sadly, none of them propagated it to the handler. Can I somehow utilize CoroutineExceptionHandler with async coroutines?

2 Answers

There are a couple of ways to make it work. First of all, as it's stated in the async documentation, it cancels the parent job on failure. That's why scope.launch { throw e } does nothing, the scope is cancelled by that time and the coroutine does not even starts.

Then as it's described in the supervised coroutines documentation, to handle uncaught exceptions in a supervised context, you should install the handler into the child's context. So e.g. the following code will pass the exception to your handler

runBlocking {
   supervisorScope {
      launch(handler){
         async.await()
      }
   }
}   

Another way to go is to install the exception handler into the root scope of your coroutine, e.g. like that

runBlocking {
   GlobalScope.launch(handler) {
      async.await()
   }.join()
}

Here I'm using GlobalScope for demonstration, but it can be any active (non-cancelled) scope.

There are many examples in the coroutine exception handling doc which I'd recommend to check and run also.

No, you can't.

async returns a Deferred, which is like a future. The exception handler doesn’t apply, because if there’s an exception, it is the return value of the Deferred.

From the docs:

In addition to that, async builder always catches all exceptions and represents them in the resulting Deferred object, so its CoroutineExceptionHandler has no effect either.

source: https://kotlinlang.org/docs/exception-handling.html#coroutineexceptionhandler

Related