kotlinkotlin-coroutines

Coroutine fails despite parent's SupervisorJob()


Why does the second coroutine gets cancelled although I have used SupervisorJob()? The code targets the JVM.

fun main() {
    val handler =
        CoroutineExceptionHandler { _, _ -> println("handling the error") }

    CoroutineScope(SupervisorJob() + handler).launch {
        println(Thread.currentThread().name)

        launch {
            println(Thread.currentThread().name)
            println("...")
            delay(500)
            throw RuntimeException("failing first coroutine")
        }

        launch {
            println(Thread.currentThread().name)
            println("fetching some data over the network")
            delay(1000)
            println("Done")
        }
    }

    Thread.sleep(2000)
}

Solution

  • From the documentation:

    Children of a supervisor job can fail independently of each other.

    The issue here is that the two inner coroutines are not children of the supervisor job, they are grand-children. The only child of the supervisor job is the coroutine you directly launch in it. Let's call it coroutine A:

    CoroutineScope(SupervisorJob() + handler).launch {
        // child of supervisor job
    }
    

    Then this child's scope is used to launch two other coroutines (B and C). The supervisor job does not propagate to child coroutines. If it would, a nested coroutine's behavior would become unpredictable. That is why failing B fails A and that, in turn cancels all other children of A, which is C.

    For your code to run as expected, you need to make both coroutines B and C children of the supervisor job. That could look like this:

    val scope = CoroutineScope(SupervisorJob() + handler)
    
    println(Thread.currentThread().name)
    
    scope.launch {
        println(Thread.currentThread().name)
        println("...")
        delay(500)
        throw RuntimeException("failing first coroutine")
    }
    
    scope.launch {
        println(Thread.currentThread().name)
        println("fetching some data over the network")
        delay(1000)
        println("Done")
    }
    
    Thread.sleep(2000)
    

    By removing A and launching B and C directly in the scope with the supervisor job, when coroutine B fails, C will not automatically be cancelled.