Hello! I have a question regarding this code: ```@...
# coroutines
d
Hello! I have a question regarding this code:
Copy code
@OptIn(DelicateCoroutinesApi::class) fun main() = runBlocking { //sampleStart
val handler = CoroutineExceptionHandler { _, exception -> 
    println("CoroutineExceptionHandler got $exception")    
}
val job = GlobalScope.launch(handler) {   
    val inner = launch { // all this stack of coroutines will get cancelled   #3
        launch {                   
            launch {
                throw IOException() // the original exception    #1
            } 
        }
    }
    try {
        inner.join()       
    } catch (e: CancellationException) {
        println("Rethrowing CancellationException with original cause")
        throw e // cancellation exception is rethrown, yet the original IOException gets to the handler    #6
    } 
}
job.join()
}
If I understand correctly, top-level launch was already cancelled by throw IOException, so what is the purpose of the next code?How it would be treated?
Copy code
try {
        inner.join()       
    } catch (e: CancellationException) {
        println("Rethrowing CancellationException with original cause")
        throw e // cancellation exception is rethrown, yet the original IOException gets to the handler    #6
    }
I also saw in the documentation “Cancellation exceptions are transparent and are unwrapped by default”, but I don’t understand how it relates to this code at all. Thank you very much for your help
c
that try/catch is simply doing a println on the caught CancellationException and rethrowing it, unchanged; it serves no functional purpose. Generally catching CancellationException is a code smell (aside from lower-level framework code, where one has a good understanding of the implications).
d
May I ask you to share how the cancellation from throw of exception to throw rethrow works in between and handled in the end? Thanks
c
Perhaps the examples in the Kotlin documentation will be of assistance.
d
I already checked them, that’s why I created this question
Just from my understanding: • the exception is thrown • It cancels launch • Second launch is cancelled • inner launch is cancelled • GlobalScope launch is cancelled • The join throw cancellation exception and it is re-thrown So the GlobalScope CoroutineScope is cancelled by root exception and have one more cancellation exception from re-thrown. What does it mean transparent and unwrapped? And how the CoroutineScope would manage both exceptions? Thanks
c
Per the docs ‘launch’ exceptions aren’t propagated they hit an exception handler.
d
it is. According to docs:
Copy code
Normally, uncaught exceptions can only result from root coroutines created using the launch builder. All children coroutines (coroutines created in the context of another Job) delegate handling of their exceptions to their parent coroutine, which also delegates to the parent, and so on until the root, so the CoroutineExceptionHandler installed in their context is never used.
https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/
@Sam , sorry to bother you, may I ask you for the help to understand this case please? Thank you!
s
I can try! Coroutine cancellation is my special subject 😄.
You're right about the error propagation mechanism. The
throw IOException()
propagates up through all three calls to
launch
, causing
job
to fail. In this case we can think of "failure" as a specific type of cancellation.
I think the first code example you shared is taken from this documentation on exception aggregation. I'm not exactly sure what this documentation is trying to convey. I think it might have been written some time ago, and the cancellation mechanism may have been refined since then.
d
Yes, you are right, this documentation!)
Thanks for your support, as always)
It’s really confusing for me)
s
The part about cancellation exceptions being "transparent" makes some amount of sense when compared to the previous code example in the documentation. • In the first code example, we can see that when a coroutine fails with an exception, then throws a second exception during its cancellation, the first exception will have the second one attached to it as a "suppressed" exception. • In the second code example (the one you shared), we have a similar scenario, except that the second exception is a cancellation exception. In this case, we only see the first exception, and there's no suppressed exception attached to it. (Not that that's clear in the output from the code).
The part about cancellation exceptions being "unwrapped" is, as far as I can tell, meaningless. Perhaps it dates from an older version of the coroutines library.
The only reason a cancellation exception is thrown at all here is because the code is trying to call a suspending function (
join()
) in a coroutine that has already failed. The
cause
of the cancellation exception is unimportant. If it gets rethrown, it will simply be ignored, because the job has already failed with a different exception.
d
Got you
Thank you very much
You are best as always
For me documentation is a big pain to understand what is going on)
s
Glad I could help! I agree that this bit of documentation is particularly confusing.
❤️ 1
Did you see that there's a survey right now about how the documentation can be improved? There's a link to it at the top of the docs site.
d
I’ll for sure add my two words there)thank you!)