Hi, guys! I have a question about this strange (o...
# coroutines
n
Hi, guys! I have a question about this strange (only for me) behavior. Just look at this two parts of very similar code: first:
Copy code
import kotlinx.coroutines.*

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, e ->
        println("Exception from handler: " + e)
    }

    val scope = CoroutineScope(Dispatchers.Default + handler)
    val launchJob = scope.launch {
        supervisorScope {
            launch {
                println("Long running task...")
                delay(500)
                if (true) {
                    throw Exception("Oops!")
                }
            }
            launch {
                delay(1000)
                println("Another child")
            }
        }
    }
    launchJob.join()

    println("Application is not crashed.")
}
second:
Copy code
import kotlinx.coroutines.*

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, e ->
        println("Exception from handler: " + e)
    }

    val scope = CoroutineScope(Dispatchers.Default + handler)
    val launchJob = scope.launch {
        supervisorScope {
            async {
                println("Long running task...")
                delay(500)
                if (true) {
                    throw Exception("Oops!")
                }
            }
            launch {
                delay(1000)
                println("Another child")
            }
        }
    }
    launchJob.join()

    println("Application is not crashed.")
}
First output:
Copy code
Long running task...
Exception from handler: java.lang.Exception: Oops!
Another child
Application is not crashed.
Second output:
Copy code
Long running task...
Another child
Application is not crashed.
Could you please explain me why CoroutineExceptionHandler did not catch exception in case when we use async inside supervisorScope?
j
I believe the idea is that with
async
you're supposed to await the result (explicitly with
await
) at some point, and that's probably where you want the exception to be thrown. I think this is confusing not only to you, IIRC there was an open issue about exception handling with async
☝️ 1
p
The CoroutineExceptionHandler docs explicitly say:
A coroutine that was created using async always catches all its exceptions and represents them in the resulting Deferred object, so it cannot result in uncaught exceptions.
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/
n
@Peter Farlow But no, just look at this code:
Copy code
import kotlinx.coroutines.*

fun main() = runBlocking {
    val handler = CoroutineExceptionHandler { _, e ->
        println("Exception from handler: " + e)
    }

    val scope = CoroutineScope(Dispatchers.Default + handler)
    val launchJob = scope.launch {
        coroutineScope {
            async {
                println("Long running task...")
                delay(500)
                if (true) {
                    throw Exception("Oops!")
                }
            }
            launch {
                delay(1000)
                println("Another child")
            }
        }
    }
    launchJob.join()

    println("Application is not crashed.")
}
Output will be:
Copy code
Long running task...
Exception from handler: java.lang.Exception: Oops!
Application is not crashed.
So async propogated his exception to parent scope (coroutineScope) without await!
s
For better or worse, that's just how
async
works. When it's a root coroutine, or a child of a supervisor job, it swallows exceptions. In all other cases, it propagates them to the parent job, regardless of whether
await
is called. I don't think there's an "explanation", other than the series of design choices that led us here. Since this behaviour changed with the introduction of structured concurrency, I suppose some of the documentation for it might be out of date or confusing.
💯 1
n
May be we can ask to add some docs for this behavior?
Okay, just reported ticket about it - https://github.com/Kotlin/kotlinx.coroutines/issues/4012