i've simplified, can anyone explain why this leads...
# coroutines
k
i've simplified, can anyone explain why this leads to an uncaught exception logged to console?
Copy code
fun main(args: Array<String>) {
    val job = SupervisorJob()
    val scope = CoroutineScope(job + Dispatchers.Default)

    val deferred = GlobalScope.async(Dispatchers.Default, start = CoroutineStart.LAZY) {
        delay(5000)
        throw Exception("boom")
    }

    scope.launch {
        try {
            deferred.await()
        } catch (e: Exception) {
            println("error awaiting: $e")
        }
    }

    runBlocking { job.cancelAndJoin() }

    Thread.sleep(10000)
}
e
you start
async
in another scope (global) so it runs independent from your supervisor job (which exception you see like Exception in thread “DefaultDispatcher-worker-1”.. ) all other coroutines are in one scope, so then you cancel
job
by
cancelAndJoin
, you also cancel
launch
(running in scope.launch)
k
sure, but why does that mean the exception goes to console now?
deferred is supposed to capture the exception in
async
e
default exception handler for coroutine, i think
k
if you comment out the cancellation you see the normal behavior where default exception handler is not invoked for
async
blocks
e
sure, because exception will be handled by another coroutine in try-catch, but with cancel this coroutine couldn’t do it
k
it's propagated the caller of
await()
. If I cancel the only caller, nothing is `await()`ing any more, so why the exception?
e
but async coroutine is not canceled and stay running
k
yes, but that doesn't mean the exception should leak to console via uncaught handler
even if I put another call to deferred.await() after the cancellation to actually receive or catch the result of it I get the exception in the console and the thrown exception from the 2nd await()
e
when job cancelled, all child coroutines canceled too, at this point scope.launch is on suspend point at await, after cancelation it starts at this suspend point and throws exception (JobCancellationException).. all above is all ok, right? async coroutine after above cancelation is not canceled, and running farther and after delay throws exception. coroutine helfself is state machine, and at exception it returnWithError. not very sure in the future, but as I understand it Exception handler catch exception and see that no coroutine is await result, so it simple throws it farther
k
but like i said, i can add another that awaits the result
and it behaves the same
e
found this:
Copy code
is CancelledContinuation -> {
                    /*
                     * If continuation was cancelled, then all further resumes must be
                     * ignored, because cancellation is asynchronous and may race with resume.
                     * Racy exception are reported so no exceptions are lost
                     *
                     * :todo: we should somehow remember the attempt to invoke resume and fail on the second attempt.
                     */
                    if (proposedUpdate is CompletedExceptionally) {
                        handleException(proposedUpdate.cause)
                    }
                    return
                }
kotlinx.coroutines.AbstractContinuation:206.kt
and then comes to: kotlinx.coroutines.CoroutineExceptionHandlerImplKt:
Copy code
internal fun handleExceptionViaHandler(context: CoroutineContext, exception: Throwable) {
    // Invoke exception handler from the context if present
    try {
        context[CoroutineExceptionHandler]?.let {
            it.handleException(context, exception)
            return
        }
    } catch (t: Throwable) {
        handleCoroutineExceptionImpl(context, handlerException(exception, t))
        return
    }

    // If handler is not present in the context or exception was thrown, fallback to the global handler
    handleCoroutineExceptionImpl(context, exception)
}
no handles is present, so it throws with handleCoroutineExceptionImpl
Copy code
internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
    // use additional extension handlers
    for (handler in handlers) {
        try {
            handler.handleException(context, exception)
        } catch (t: Throwable) {
            // Use thread's handler if custom handler failed to handle exception
            val currentThread = Thread.currentThread()
            currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t))
        }
    }

    // use thread's handler
    val currentThread = Thread.currentThread()
    currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
}
e
thanks for reporting!