Does the scope handle exceptions/failures silently under the hood?

Viewed 265

I have the following snippet for testing purposes;

fun main() {

   val myScope = CoroutineScope(Dispatchers.Default) + Job()

   myScope.launch {

       val job = async {
           delay(1000)
           throw RuntimeException("shiiiet")
       }

       try {
           job.await()
       } catch (ret: RuntimeException){
           throw RuntimeException("yooo!")
       }
   }

    try {
        Thread.sleep(5000)
    } catch(e: Exception){

    }

    println("wohoooo!") 
}

I thought the flow would never reach the last "wohooo!" line but I was wrong. I see it's printed on the screen. The reason I had in my mind that launch would propagate the exception to the parent scope and since the parent scope does not handle it, it would crash the JVM by the time it reaches the print statement.

Is this because the parent scope got cancelled once it's child failed, received a CancellationException and it was ignored?

1 Answers

You tried multiple throwand catch approaches in your example.

async works as expected - when you await for it, you can catch the exception. But if you launch a co-routine, the default Thread.uncaughtExceptionHandler just prints the result to console.

Even if you do

myScope.launch {
    ....
}.invokeOnCompletion { e -> println("Exception: $e") }

you still get the result additionally on console.

The propagation rules and the different types of calls to handle the exceptions are explained here.

An example on how to catch an exception in the "main" co-routine:

fun main() = runBlocking {
    try {
        GlobalScope.launch {
            delay(10)
            throw Exception("You don't catch me in main.")
        }
        launch {
            delay(10)
            throw Exception("You catch me in main.")
        }
        delay(100)
        println("Never reached.")
    } catch(e: Exception) {
        println("Caught in main: ${e.cause}")
    }
    println("The end")
}
Related