hey, qq on exception handling. How do I make corou...
# coroutines
d
hey, qq on exception handling. How do I make coroutine stop if a child coroutine throws an exception:
Copy code
launch {
            launch {
                throw RuntimeException()
            }
            while (true) {
                println("123123")
            }
        }
I want parent launch to exit with an exception, but not sure how to
d
Take a look at 'Structured concurrency' post from @elizarov : https://medium.com/@elizarov/structured-concurrency-722d765aa952 That's precisely what you want.
r
You need to make sure the coroutine suspends to allow the launch to throw an exception. A quick fix for you would add
yield()
in your
while
loop.
l
or just
while(isActive)
instead of
while(true)
r
@louiscad Actually, that won't work in this case. The check to
isActive
doesn't suspend, so if the `launch`ed coroutine never gets a chance to run, it will never throw and thus the parent will stay active. It all comes down to how the `launch`ed coroutine is dispatched.
l
@Ruckus I think the scope will forward it to parent scope.
while(true)
is just plain dangerous here as nothing can stop it
r
On the contrary, if you add a
yield()
statement, the
while (true)
block will indeed end.
while (true)
isn't bad in and of itself, and is used in many places in kotlinx.coroutines. You just have to make sure you're using it correctly.
l
Yes, but
while(isActive)
should still work, and child scope should forward to parent scope, unless it's a
CancellationException
while(isActive)
is more efficient than suspending and redispatching everytime with
yield()
. That's why it's there on
CoroutineScope
and
CoroutineContext
r
Try it out, you'll find this will never end:
Copy code
fun main(args: Array<String>) = runBlocking<Unit> {
    launch {
        throw RuntimeException()
    }
    while (isActive) {
        println("123123")
    }
}
whereas this will indeed:
Copy code
fun main(args: Array<String>) = runBlocking<Unit> {
    launch {
        throw RuntimeException()
    }
    while (true) {
        println("123123")
        yield()
    }
}
e
Cancellation is cooperative. You block the main thread with an infinite loop and nothing else can use it.
r
That's what I was trying to say, thanks 🙂
l
Oh yeah, I understand why it would never become inactive now, it's because you may block the dispatcher (possibly/likely single-threaded) in charge to propagate the
Throwable
đź‘Ť 1
g
Yeah, this is already second discussion with the same case: while(true) in runBlocking coroutine Probably make sense to cover such pitfall in docs
l
Maybe IDE inspections could be added for both
while(isActive)
and
while(true)
called that have no suspension points
d
thank you for responses, I guess I need to read up more on the coroutines. I have a follow up question: How do I propogate the exception thrown in the child? so the parent coroutine will not just exit silently, but rethrow the error?
e
That happens automatically. If child fails, parent cannot complete normally.
d
I do this
Copy code
@Test
    fun test() {
        launch {
            launch {
                throw RuntimeException()
            }
            while (true) {
                println("123123")
                yield()
            }
        }
    }
in a test, but it just passes. I was assuming it should fail
is it junit artifact?
let me try win the main function
e
It passed, because your top-level coroutine is
launch
. If would print exception on the console if you give it time to.
If your top-level coroutine is
runBlocking
, then it would crash
d
why there is a difference in the behaviour between these 2?
I feel like I am missing some essential point about coroutines, but cannot figure out what
e
Because
launch
is fire-and-forget. It cannot “throw exception”. It returns immediately.
đź‘Ś 1
d
what should I use for something that would do something in the background but if it fails it crashes main thread?
e
That is what
runBlocking
does.
You do
runBlocking
in your main thread and do as much other stuff in background. So,
runBlocking
waits this background stuff, and if that background stuff fails, then
runBlocking
fails.
d
but then I cannot proceed past the runBlocking call. Here is my use case:
Copy code
init {
        launch{
            while (true) {
                delay((expiryTime - BUFFER_SECONDS) * 1000)
                refreshToken()
            }
        }
    }
e
Exactly. You cannot crash your main thread, unless it waits.
What are you trying to achieve? Can you elaborate?
d
I create an instance of the class with a variable and want to update that variable in a background using coroutines, but I also want main thread to crash or at least to be able to handle case when updating the variable fails
(in my case it's a ktor client feature that updates expired cached auth tokens and provides them when making request)
e
Updating variable in background does not seem like a good idea, since that is a shared mutable state. What are you trying to achieve with that?
I see….
d
it's not too critical if there will be a race condition because I refresh tokens some time before expiration time but the old tokens are still usable
e
You can do
GlobalScope.launch { ... }
and if that crashes then you’ll get stacktrace printed on console. I don’t see how you can crash main thread. Unfortunately, ktor does not (yet) expose its “application scope”, so there is no way (yet) to make the whole application crash on that error
d
hmm
is there other way you see my use case implemented?
e
GlobalScope
seems to be a perfect fit. It is designed for this kind of “global background tasks”.
d
but I will have to handle failed scenario some other way, rather than crashing the application, right?
g
If you want to handle fail just wrap code inside of your
launch
with try/catch and handle how you want
e
Alternatively you can
GlobalScope.launch(CoroutineExceptionHandler { /* handle here */ }) { /* code here */ }
. That will not only catch error in the code, but also if you launch any child coroutines from inside that code, then their errors will get handled, too.
d
cool, one last question: can mix coroutines versions? I am writing a library for ktor, latest stable version of which uses older coroutines version, that doesn't have GlobalScope and stuff like that. Can I use newer coroutines library with ktor?
or is it a question for #ktor?
e
You cannot until it hits 1.0 release. You should migrate to the most recent Ktor (beta version) and the most recent coroutines
d
eh, might be problematic, as I am not the owner of other clients of the library
tho old launch launches in the global scope?
e
Yes.
GlobalScope
was the default
(but now it has to explicitly specified)
d
yeap, cool cool, thank you, all this was very helpful