https://kotlinlang.org logo
#coroutines
Title
# coroutines
k

kevin.cianfarini

06/22/2023, 7:28 PM
Should I be using github discussions for questions about coroutines? I noticed I was the first person to post a discussion. https://github.com/Kotlin/kotlinx.coroutines/discussions/3790
j

Joffrey

06/22/2023, 7:35 PM
Usually people ask questions here in #coroutines or on StackOverflow, but maybe that's an acceptable place too :)
s

Sam

06/22/2023, 7:43 PM
The lack of other discussions on GitHub probably means people won’t be monitoring it, so you’re more likely to get a response here. And since you’re here, here’s a response 😄. • To terminate a flow “normally”, i.e. without propagating an error, you should use
takeWhile
or
transformWhile
. A flow isn’t a coroutine and doesn’t have a job, so trying to exit it by “cancelling” something isn’t apropos. • Yes, there’s a difference between throwing a cancellation exception and cancelling the job. Cancelling the job puts the job into a cancelled state immediately, such that all subsequent attempts to suspend the coroutine will throw a cancellation exception. You can’t recover from it because you can’t unset the job’s cancellation state. Merely throwing a cancellation exception without cancelling the job does not put the job into the cancelled state until the job is actually completed, meaning you can, if you choose, recover from this state by catching the exception and continuing as if nothing had happened.
k

kevin.cianfarini

06/22/2023, 7:50 PM
Yes, there’s a difference between throwing a cancellation exception and cancelling the job.
Perfect! This is the info I needed. 🙂 Thank you.
takeWhile
or
transformWhile
.
Unfortunately this wasn’t an option for me. Library code which I don’t control is calling
.single
on the flow. takeWhile or transformWhile will succeed but not produce a value, and thus
single
would fail with no such element exception.
s

Sam

06/22/2023, 7:54 PM
Eek, sounds like a frustrating situation. Can you catch exceptions coming out of the library code? If so I would make a custom exception to throw from the flow and catch from outside. Throwing a cancellation exception out into the library code isn’t ideal because it could cause other important coroutines to die silently 😬
k

kevin.cianfarini

06/22/2023, 7:55 PM
I can but this is Apollo and thus our networking library. It’s easier just to cancel it and tear the whole show down. When this “bad value” happens and we cancel the coroutine from the inside out, we do that because we trigger a side effect that logs a customer out.
I don’t want to impose catching this special exception everywhere because, since it would be thrown from an interceptor, it would need to be caught everywhere.
And Kotlin doesn’t have checked exceptions which makes handling something like this particularly error prone.
So we instead opt to cancel the in flight network request and destroy our entire navigation graph and send the customer back to the auth screen.
Alternatively we could have a global exception handler that listens for that special exception and triggers the side effect, but doing this felt less implicit.
s

Sam

06/22/2023, 8:03 PM
My worry would be whether you can actually be sure what you’re going to end up cancelling. Is the coroutine itself created by the library code too?
k

kevin.cianfarini

06/22/2023, 8:03 PM
Nope, Apollo exposes either a flow of values or a suspending function. I control the actual coroutine
(The suspending function is just shorthand for
Flow.single
which is the problem here)
s

Sam

06/22/2023, 8:09 PM
Being in control of the coroutine does make it a bit better 👍. Cancelling a coroutine you don’t control is definitely not safe, but cancelling your own coroutine is okay. Though as you said it’s still a bit unexpected for a coroutine to cancel itself.
I’m out of advice but I do have some recommended background reading 😄 https://medium.com/better-programming/the-silent-killer-thats-crashing-your-coroutines-9171d1e8f79b
k

kevin.cianfarini

06/22/2023, 8:11 PM
We’ve had similar issues to this, too.
We make sure to be explicit about what exceptions we’re catching. You should never catch
Exception
or
Throwable
.
for that reason I feel that
runCatching
is a bad API to expose in Kotlin since it breaks cancellation
Though it is particularly problematic in the cases where you actually want to catch
IllegalStateException
. Maybe in Kotlinx-coroutines v2 They’ll opt to have a standalone cancellation exception instead of one that has an inheritance hierarchy