https://kotlinlang.org logo
Title
s

Sam

04/08/2019, 12:33 AM
Why is the handler ignored in this case?
fun main() {

    val handler = CoroutineExceptionHandler { _, throwable ->
        println("Exception $throwable")
    }

    runBlocking {

        val scope = CoroutineScope( coroutineContext + handler )

        scope.launch {
            throw Exception()
        }.join()
    }

    println( "main done" )
}
o

octylFractal

04/08/2019, 4:07 AM
I believe unless you use
supervisorJob
, child jobs propagate exceptions outwards: https://kotlinlang.org/docs/reference/coroutines/exception-handling.html#exceptions-in-supervised-coroutines
indeed, from earlier on that page:
If a coroutine encounters exception other than CancellationException, it cancels its parent with that exception. This behaviour cannot be overridden and is used to provide stable coroutines hierarchies for structured concurrency which do not depend on CoroutineExceptionHandler implementation. The original exception is handled by the parent when all its children terminate.
g

gildor

04/08/2019, 12:17 PM
+1 to Kenzie, just want to add that from this example hard to recommend correct solution without some real example
s

Sam

04/08/2019, 1:05 PM
This was just to get an idea of the exception handler
@octylFractal Setting up the scope without context does invoke the handler which is what led me to try add a context and see what happens
val scope = CoroutineScope( handler )
g

gildor

04/08/2019, 1:25 PM
this approach just incorrect in general with Job
s

Sam

04/08/2019, 1:26 PM
you mean without a job?
g

gildor

04/08/2019, 1:26 PM
you cannot prevent scope with Job to throw exception on child error
you have to use SupervisorJob instead
s

Sam

04/08/2019, 1:28 PM
Tried this and handler is invoked and scope doesn't rethrow. so what am i missing?
val scope = CoroutineScope( Job() + handler )
g

gildor

04/08/2019, 1:28 PM
again, hard to recommend something without real use case
again, it’s incorrect
this is how Job works
s

Sam

04/08/2019, 1:29 PM
Not about a use case, just figuring out how exceptions work
g

gildor

04/08/2019, 1:29 PM
I mean it’s not incorrect by itself
it’s just doesn’t work how you expect with Job
I just want tell you, that there are many unnecessary components in your code and not how it should be used in any real code
For example I can rewrite it like this:
fun main() {
    try {
        runBlocking {
            launch {
                throw Exception()
            }
        }
    } catch(throwable: Exception) {
        println("Exception $throwable")
    }

    println( "main done" )
}
This will be the same code as above that uses coroutines and exception handling properly, how you expect it
s

Sam

04/08/2019, 1:36 PM
Got it, I was just curious at what factors influenced the behavior one way or the other in my case
g

gildor

04/08/2019, 1:39 PM
Yes, this covered in doc that Kenzie sent in his first comment
also, in your case you use runBlocking which throws exception that impossible prevent without wrapping code inside to try/catch (for regular or suspend code), or without supervisorJob, which is covered in section “Exceptions in supervised coroutines” of this doc
s

Sam

04/08/2019, 2:30 PM
Yeah, so looks like using coroutineContext from runBlocking's scope creates a parent-child coroutine and that explains the bail out. Without coroutineContext, the coroutine launched with a new scope behaves like a GlobalScope and uses the handler
g

gildor

04/08/2019, 4:42 PM
Yes, correct, also in general usage of CoroutineScope() builder often a sign that something went wrong