https://kotlinlang.org logo
Title
d

Dias

10/16/2018, 5:06 PM
hey, qq on exception handling. How do I make coroutine stop if a child coroutine throws an exception:
launch {
            launch {
                throw RuntimeException()
            }
            while (true) {
                println("123123")
            }
        }
I want parent launch to exit with an exception, but not sure how to
d

dekans

10/16/2018, 5:14 PM
Take a look at 'Structured concurrency' post from @elizarov : https://medium.com/@elizarov/structured-concurrency-722d765aa952 That's precisely what you want.
r

Ruckus

10/16/2018, 5:16 PM
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

louiscad

10/16/2018, 5:28 PM
or just
while(isActive)
instead of
while(true)
r

Ruckus

10/16/2018, 5:30 PM
@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

louiscad

10/16/2018, 5:32 PM
@Ruckus I think the scope will forward it to parent scope.
while(true)
is just plain dangerous here as nothing can stop it
r

Ruckus

10/16/2018, 5:34 PM
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

louiscad

10/16/2018, 5:35 PM
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

Ruckus

10/16/2018, 5:37 PM
Try it out, you'll find this will never end:
fun main(args: Array<String>) = runBlocking<Unit> {
    launch {
        throw RuntimeException()
    }
    while (isActive) {
        println("123123")
    }
}
whereas this will indeed:
fun main(args: Array<String>) = runBlocking<Unit> {
    launch {
        throw RuntimeException()
    }
    while (true) {
        println("123123")
        yield()
    }
}
e

elizarov

10/16/2018, 5:39 PM
Cancellation is cooperative. You block the main thread with an infinite loop and nothing else can use it.
r

Ruckus

10/16/2018, 5:40 PM
That's what I was trying to say, thanks 🙂
l

louiscad

10/16/2018, 6:01 PM
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

gildor

10/16/2018, 6:01 PM
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

louiscad

10/16/2018, 6:04 PM
Maybe IDE inspections could be added for both
while(isActive)
and
while(true)
called that have no suspension points
d

Dias

10/17/2018, 8:49 AM
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

elizarov

10/17/2018, 8:50 AM
That happens automatically. If child fails, parent cannot complete normally.
d

Dias

10/17/2018, 8:51 AM
I do this
@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

elizarov

10/17/2018, 8:52 AM
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

Dias

10/17/2018, 8:53 AM
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

elizarov

10/17/2018, 8:53 AM
Because
launch
is fire-and-forget. It cannot “throw exception”. It returns immediately.
👌 1
d

Dias

10/17/2018, 8:55 AM
what should I use for something that would do something in the background but if it fails it crashes main thread?
e

elizarov

10/17/2018, 8:56 AM
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

Dias

10/17/2018, 9:00 AM
but then I cannot proceed past the runBlocking call. Here is my use case:
init {
        launch{
            while (true) {
                delay((expiryTime - BUFFER_SECONDS) * 1000)
                refreshToken()
            }
        }
    }
e

elizarov

10/17/2018, 9:00 AM
Exactly. You cannot crash your main thread, unless it waits.
What are you trying to achieve? Can you elaborate?
d

Dias

10/17/2018, 9:03 AM
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

elizarov

10/17/2018, 9:07 AM
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

Dias

10/17/2018, 9:09 AM
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

elizarov

10/17/2018, 9:12 AM
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

Dias

10/17/2018, 9:14 AM
hmm
is there other way you see my use case implemented?
e

elizarov

10/17/2018, 9:20 AM
GlobalScope
seems to be a perfect fit. It is designed for this kind of “global background tasks”.
d

Dias

10/17/2018, 9:21 AM
but I will have to handle failed scenario some other way, rather than crashing the application, right?
g

gildor

10/17/2018, 9:22 AM
If you want to handle fail just wrap code inside of your
launch
with try/catch and handle how you want
e

elizarov

10/17/2018, 9:24 AM
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

Dias

10/17/2018, 9:38 AM
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

elizarov

10/17/2018, 9:39 AM
You cannot until it hits 1.0 release. You should migrate to the most recent Ktor (beta version) and the most recent coroutines
d

Dias

10/17/2018, 9:40 AM
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

elizarov

10/17/2018, 9:41 AM
Yes.
GlobalScope
was the default
(but now it has to explicitly specified)
d

Dias

10/17/2018, 9:42 AM
yeap, cool cool, thank you, all this was very helpful