androidkotlinkotlin-coroutineskotlin-multiplatformkotlin-multiplatform-mobile

Why is exception handled by the handler of a non-root, non-supervisor coroutine?


I was testing the exception handling mechanism of coroutines with this test function:

suspend fun test(){
    supervisorScope {launch(createExceptionHandler(1)) {
        coroutineScope {launch(createExceptionHandler(2)) {
            supervisorScope {launch { //SUPERVISOR WITH NO HANDLER
                coroutineScope {launch(createExceptionHandler(4)) {
                    coroutineScope {launch(createExceptionHandler(5)) {
                        throw Exception("Testing")
                    }}
                }}
            }}
        }}
    }}
}

fun createExceptionHandler(i: Int) = CoroutineExceptionHandler { _, throwable ->
    "---> exception handler #$i caught: ${throwable}".log()
}

Result:

---> exception handler #2 caught: java.lang.Exception: Testing

I was expecting handler #1 to catch the exception, and to my surprise, it was handler #2 who caught it!


Reading the docs, I expect handler #2, #4, #5 to be completely ignored:

... In particular, 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.

What I understand was that exceptions stop propagating when it reaches the root, or a supervisorScope with an exception handler. So I thought handler #1 would have handled the exception.

This test function (2) seems to confirm my beliefs:

suspend fun test2(){
    supervisorScope {launch(createExceptionHandler(1)) {
        supervisorScope {launch(createExceptionHandler(2)) {
            supervisorScope {launch {
                supervisorScope {launch {
                    supervisorScope {launch {
                        throw Exception("Testing")
                    }}
                }}
            }}
        }}
    }}
}

Result:

---> exception handler #2 caught: java.lang.Exception: Testing

I have read numerous guides online on exception propagation and handling and I am quite stuck on this...

Any clues would help, thanks for reading!


Solution

  • In your first example, supervisorScope that launches a coroutine without an exception handler actually inherits the exception handler from the outer scope. You can verify by printing the CoroutineContext after launch.