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!
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
.