As the title says, why do suspending functions throw exceptions in finally?
With regular functions, the finally-block executes all of them:
import kotlinx.coroutines.*
fun main() {
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
val job = GlobalScope.launch(handler) {
launch {
// the first child
try {
println("inside try")
delay(1000)
} finally {
println("Children are cancelled, but exception is not handled until all children terminate")
Thread.sleep(1000)
println("thread.sleep executed")
//foo()
println("The first child finished its non cancellable block")
}
}
launch {
// the second child
delay(10)
println("Second child throws an exception")
throw ArithmeticException()
}
}
Thread.sleep(1000000)
println("complete")
}
Here, for example, when I do Thread.sleep(1000) it prints:
"The first child finished its non cancellable block"
but if I change that line to delay(1000), it does not.
From my understanding, in a finally-block, the exception, if it exists, is thrown after executing the entire block.
But in this case, delay causes this exception to be thrown early.
On the other hand, Thread.sleep does not.
Can someone help explain?
Suspending functions in Kotlin work differently than blocking function.
When you cancel a Job, at the first suspension after the cancellation the execution will be stopped, even if you are in a finally block. If you use Thread.sleep(1000) instead of delay(1000) in your finally block, there are no suspensions taking place, because Thread.sleep() is blocking, not suspending, so your whole finally block gets executed.
Note that using blocking functions inside of suspending functions is an anti-pattern and should be avoided!!
To achieve this desired behavior without using blocking functions, use withContext(NonCancellable) {...} as described here.
Your example code should look like this:
fun main() {
val handler = CoroutineExceptionHandler { _, exception ->
println("Caught $exception")
}
val job = GlobalScope.launch(handler) {
launch {
// the first child
try {
println("inside try")
delay(1000000)
} finally {
withContext(NonCancellable) {
println("Children are cancelled, but exception is not handled until all children terminate")
delay(1000) // This suspension cannot be cancelled
println("delay executed")
//foo()
println("The first child finished its non cancellable block")
}
}
}
launch {
// the second child
delay(10)
println("Second child throws an exception")
throw ArithmeticException()
}
}
Thread.sleep(1000000)
println("complete")
}
The output:
inside try
Second child throws an exception
Children are cancelled, but exception is not handled until all children terminate
delay executed
The first child finished its non cancellable block
Caught java.lang.ArithmeticException
From my understanding, in a finally-block, the exception, if it exists, is thrown after executing the entire block.
This is not true. If a finally block throws an exception, it causes the finally block to terminate abruptly with that exception. Any exception that was thrown within try is thus discarded. This is exactly what happens in your case: the finally block of the first child coroutine receives a CancellationException on the delay(1000) line. Thread.sleep(1000) is a blocking, non-cancellable function, therefore it doesn't observe the cancellation.
You probably mixed up this with the fact that, if the try block throws an exception, then first the complete finally block is executed before throwing the exception. The finally block is required to complete normally in order for this to happen.
So I believe you aren't describing any difference in the behavior of plain and suspendable functions.
Related
I've been reading about handling exceptions with coroutines but there are a few things unclear for me yet.
When does a CancellationException occur? Do I have to assign the coroutine to a job and then, call job.cancel() in order for it to happen?
Looking at my code, will it ever catched? Or is it the first catch block useless?
fun invoke(params: Params, onResult: (UseCaseResponse<Type>)?) {
CoroutineScope(Dispatchers.Main).launch {
try {
val result = run(params)
onResult?.onSuccess(result)
Log.d(TAG, "Response: $result")
} catch (e: CancellationException) {
Log.d(TAG, "Error: $e")
onResult?.onError(apiErrorHandler.traceErrorException(e))
} catch (e: Exception) {
Log.d(TAG, "Error: $e cause: ${e.cause}")
onResult?.onError(apiErrorHandler.traceErrorException(e))
}
}
}
It may sound as a stupid answer, but CancellationException occur when someone is throwing it.
Basically, yes, this exception should be thrown when the coroutine (or its parent coroutine) is canceled (yes, via job.cancel()). But it is the responsibility of the engineer, who writes suspend function to check, whether it was canceled and act respectfully (throwing CancellationException, for instance, as all suspending functions in kotlinx.coroutines do). So whether or not your code catches CancellationException depends on what happens inside run(params).
See Cancellation is cooperative and Making computation code cancellable for more information and code examples.
I have been trying to apply coroutine to my android app but found some strange thing about async coroutine. Base on this article
val deferred = async { … }
deferred.cancel()
val result = deferred.await() // throws JobCancellationException!
If you cancel the deferred before await code is called, it will throw exception. It seems like it just doesn't allow you to cancel an async coroutine. How do I cancel the deferred without throwing an exception?
Or the only way is just to add try-catch around every await? But that seems verbose to me. Is there any cleaner approach to it?
Calling await() after cancel() leads to CancellationException. From the docs of await() method:
This suspending function is cancellable. If the Job of the current
coroutine is cancelled or completed while this suspending function is
waiting, this function immediately resumes with CancellationException.
CancellationException is thrown "silently" without crashing an app, we can read that from the docs:
If exception is CancellationException then it is ignored (because that
is the supposed mechanism to cancel the running coroutine)
If you want somehow handle the exception, cleanup resources or want your code continue execution after calling await() use try-catch block as usual:
val deferred = async { ... }
deferred.cancel()
try {
val result = deferred.await()
} catch (e: CancellationException) {
// handle CancellationException if need
} finally {
// make some cleanup if need
}
// ... other code which will be executed if `await()` throws `CancellationException`
If you want to reuse a CoroutineScope after one of its jobs is cancelled, use a SupervisorJob for it. The SupervisorJob is not cancelled when its childs get cancelled.
val reuseableScope = CoroutineScope(Dispatchers.IO + SupervisorJob())
fun main() = runBlocking {
var i = 1
var job = launch (Dispatchers.Default){
println("Thread name = ${Thread.currentThread().name}")
while (i < 10) { // replace i < 10 to isActive to make coroutine cancellable
delay(500L)
// runBlocking { delay(500L) }
println("$isActive ${i++}")
}
}
println("Thread name = ${Thread.currentThread().name}")
delay(2000L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
output
Thread name = main
Thread name = DefaultDispatcher-worker-1
true 1
true 2
true 3
main: I'm tired of waiting!
main: Now I can quit.
if i use runBlocking { delay(500L) } then the above co-routine is not cancelable. So, it will print all values upto 9.
but when i use delay(500L) automatically the co-routine can be cancelled. Why ?
delay doesn't actually do anything on its own, it just schedules the coroutine to be resumed at a later point in time. Continuations can, of course, be cancelled at any time.
runBlocking, on the other hand, actually blocks a thread (which is why the compiler will complain about a blocking operation within a coroutine, and why you should never use runBlocking outside of e.g. unit tests).
Note: Since main can now be a suspending function, there's generally no need to use it whatsoever in your core application code.
This function should not be used from a coroutine
runBlocking
Coroutines, like threads, are not truly interruptible; they have to rely on cooperative cancellation (see also why stopping threads is bad). This means that cancellation doesn't actually do anything either; when you cancel a context, it simply notifies all of its child contexts to cancel themselves, but any code that is still running will continue to run until a cancellation check is reached.
It is important to realize that runBlocking is not a suspend function. It cannot be paused, resumed, or cancelled. The parent context is not passed to it by default (it receives an EmptyCoroutineContext), so the coroutine used for the execution of runBlocking won't react to anything that happens upstream.
When you write
while (i < 10) {
runBlocking { delay(500L) }
println("$isActive ${i++}")
}
there are no operations here that are cancellable. Therefore, the code never checks whether its context has been cancelled, so it will continue until it finishes.
delay, however, is cancellable; as soon as its parent context is cancelled, it resumes immediately and throws an exception (i.e., it stops.)
Take a look at the generated code:
#Nullable
public final Object invokeSuspend(#NotNull Object $result) {
switch (this.label) {
case 0:
while (i.element < 10) {
BuildersKt.runBlocking$default( ... );
...
System.out.println(var3);
}
return Unit.INSTANCE;
default:
throw new IllegalStateException( ... );
}
}
Contrast this
while (i.element < 10) {
BuildersKt.runBlocking$default( ... );
...
System.out.println(var3);
}
with
do {
...
System.out.println(var3);
if (i.element >= 10) {
return Unit.INSTANCE;
}
...
} while (DelayKt.delay(500L, this) != var5);
Variable declarations and arguments omitted (...) for brevity.
runBlocking will terminate early if the current thread is interrupted, but again this is the exact same cooperative mechanism, except that it operates at the level of the thread rather than on a coroutine.
The official documentation states:
All the suspending functions in kotlinx.coroutines are cancellable.
and delay is one of them.
You can check that here.
I think the real question should be: Why a nested runBlocking is not cancellable? at least an attempt to create a new coroutine with runBlocking when isActive is false should fail, altough making a coroutine cooperative is your responsability. Besides runBlocking shouldn't be used in the first place.
Turns out if you pass this.coroutineContext as CoroutineContext to runBlocking, it gets cancelled:
fun main() = runBlocking {
var i = 1
var job = launch (Dispatchers.Default){
println("Thread name = ${Thread.currentThread().name}")
while (i < 10) { // replace i < 10 to isActive to make coroutine cancellable
runBlocking(this.coroutineContext) { delay(500L) }
println("$isActive ${i++}")
}
}
println("Thread name = ${Thread.currentThread().name}")
delay(2000L) // delay a bit
println("main: I'm tired of waiting!")
job.cancelAndJoin() // cancels the job and waits for its completion
println("main: Now I can quit.")
}
I modified your code a little
try {
while (i < 10) { // replace i < 10 to isActive to make coroutine cancellable
delay(500L)
println("$isActive ${i++}")
}
} catch (e : Exception){
println("Exception $e")
if (e is CancellationException) throw e
}
the output
Thread name = main
Thread name = DefaultDispatcher-worker-1
true 1
true 2
true 3
main: I'm tired of waiting!
Exception kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job=StandaloneCoroutine{Cancelling}#40bcb892
main: Now I can quit.
can you see the exception StandaloneCoroutine was cancelled its because,
If the Job of the current coroutine is cancelled or completed while this suspending function is waiting i.e delay(500L), this function immediately resumes with CancellationException.
So, the point is if you add a suspending function inside your launch it can be cancellable.
you can try that with user defined suspend fun also
Can someone explain what exactly is the difference between these two?
When do you use one over the other?
Thanks in advance.
The best way to explain the difference is to explain the mechanism of coroutineScope. Consider this code:
suspend fun main() = println(compute())
suspend fun compute(): String = coroutineScope {
val color = async { delay(60_000); "purple" }
val height = async<Double> { delay(100); throw HttpException() }
"A %s box %.1f inches tall".format(color.await(), height.await())
}
compute() "fetches" two things from the network (imagine the delays are actually network operations) and combines them into a string description. In this case the first fetch is taking a long time, but succeeds in the end; the second one fails almost right away, after 100 milliseconds.
What behavior would you like for the above code?
Would you like to color.await() for a minute, only to realize that the other network call has long failed?
Or perhaps you'd like the compute() function to realize after 100 ms that one of its network calls has failed and immediately fail itself?
With supervisorScope you're getting 1., with coroutineScope you're getting 2.
The behavior of 2. means that, even though async doesn't itself throw the exception (it just completes the Deferred you got from it), the failure immediately cancels its coroutine, which cancels the parent, which then cancels all the other children.
This behavior can be weird when you're unaware of it. If you go and catch the exception from await(), you'll think you've recovered from it, but you haven't. The entire coroutine scope is still being cancelled. In some cases there's a legitimate reason you don't want it: that's when you'll use supervisorScope.
A Few More Points
Let's make two changes to our program: use supervisorScope as discussed, but also swap the order of awaiting on child coroutines:
suspend fun main() = println(compute())
suspend fun compute(): String = supervisorScope {
val color = async { delay(60_000); "purple" }
val height = async<Double> { delay(100); throw HttpException() }
"The box is %.1f inches tall and it's %s".format(height.await(), color.await())
}
Now we first await on the short-lived, failing height coroutine. When run, this program produces an exception after 100 ms and doesn't seem to await on color at all, even though we are using supervisorScope. This seems to contradict the contract of supervisorScope.
What is actually happening is that height.await() throws the exception as well, an event distinct from the underlying coroutine throwing it.
Since we aren't handling the exception, it escapes from the top-level block of supervisorScope and makes it complete abruptly. This condition — distinct from a child coroutine completing abruptly — makes supervisorScope cancel all its child coroutines, but it still awaits on all of them to complete.
So let's add exception handling around the awaits:
suspend fun compute(): String = supervisorScope {
val color = async { delay(60_000); "purple" }
val height = async<Double> { delay(100); throw Exception() }
try {
"The box is %.1f inches tall and it's %s".format(height.await(), color.await())
} catch (e: Exception) {
"there was an error"
}
}
Now the program does nothing for 60 seconds, awaiting the completion of color, just as described.
Or, in another variation, let's remove exception handling around awaits, but make the color coroutine handle the CancellationException, wait for 2 seconds, and then complete:
suspend fun compute(): String = coroutineScope {
val color = async {
try {
delay(60_000); "purple"
} catch (e: CancellationException) {
withContext(NonCancellable) { delay(2_000) }
println("color got cancelled")
"got error"
}
}
val height = async<Double> { delay(100); throw Exception() }
"The box is %.1f inches tall and it's %s".format(height.await(), color.await())
}
This does nothing for 2.1 seconds, then prints "color got cancelled", and then completes with a top-level exception — proving that the child coroutines are indeed awaited on even when the top-level block crashes.
I think Roman Elizarov explain it quite in details, but to make it short:
Coroutines create the following kind of hierarchy:
Parent Coroutine
Child coroutine 1
Child coroutine 2
...
Child coroutine N
Assume that "Coroutine i" fails. What do you want to happen with its parent?
If you want for its parent to also fail, use coroutineScope. That's what structured concurrency is all about.
But if you don't want it to fail, for example child was some kind of background task which can be started again, then use supervisorScope.
coroutineScope -> The scope and all its children fail whenever any of the children fails
supervisorScope -> The scope and all its children do NOT fail whenever any of the children fails
As #N1hk mentioned, if you use async the order of calling await matters. And if you're depending on a result from both asynch..await blocks, then it makes sense to cancel as early as possible so that's not an ideal example for supervisorScope
The difference is more apparent when using launch and join:
fun main() = runBlocking {
supervisorScope {
val task1 = launch {
println("Task 1 started")
delay(100)
if (true) throw Exception("Oops!")
println("Task 1 completed!")
}
val task2 = launch {
println("Task 2 started")
delay(1000)
println("Task 2 completed!")
}
listOf(task1, task2).joinAll()
println("Finished waiting for both tasks")
}
print("Done!")
}
With supervisorScope, the output would be:
Task 1 started
Task 2 started
Exception in thread "main" java.lang.Exception: Oops!
...
Task 2 completed!
Finished waiting for both tasks
Done!
With coroutineScope the output would be just:
Task 1 started
Task 2 started
CoroutineScope -> Cancel whenever any of its children fail.
SupervisorScope -> If we want to continue with the other tasks even when one fails, we go with the supervisorScope. A supervisorScope won’t cancel other children when one of them fails.
Here is a useful link for understanding of coroutine in details:
https://blog.mindorks.com/mastering-kotlin-coroutines-in-android-step-by-step-guide
The following test succeeds with Process finished with exit code 0. Note, this test does print the exception to the logs, but does not fail the test (which is the behavior I want).
#Test
fun why_does_this_test_pass() {
val job = launch(Unconfined) {
throw IllegalStateException("why does this exception not fail the test?")
}
// because of `Unconfined` dispatcher, exception is thrown before test function completes
}
As expected, this test fails with Process finished with exit code 255
#Test
fun as_expected_this_test_fails() {
throw IllegalStateException("this exception fails the test")
}
Why do these tests not behave the same way?
Compare your test with the following one that does not use any coroutines, but starts a new thread instead:
#Test
fun why_does_this_test_pass() {
val job = thread { // <-- NOTE: Changed here
throw IllegalStateException("why does this exception not fail the test?")
}
// NOTE: No need for runBlocking any more
job.join() // ensures exception is thrown before test function completes
}
What happens here? Just like the test with launch, this test passes if you run it, but the exception gets printed on the console.
So, using launch to start a new coroutine is very much like using thread to start a new thread. If it fails, the error gets handled by uncaught exception handler in thread and by CoroutineExceptionHandler (see it in the docs) by launch. Exceptions in launch are not swallowed, but are handled by the coroutine exception handler.
If you want exception to propagate to the test, you shall replace launch with async and replace join with await in your code. See also this question: What is the difference between launch/join and async/await in Kotlin coroutines
UPDATE: Kotlin coroutines had recently introduced the concept of "Structured Concurrency" to avoid this kind of exception loss. The code in this question does not compile anymore. To compile it, you'd have to either explicitly say GlobalScope.launch (as in "I confirm that it Ok to loose my exceptions, here is my signature") or wrap the test into runBlocking { ... }, in which case exception is not lost.
I was able to create an exception throwing CoroutineContext for tests.
val coroutineContext = Unconfined + CoroutineExceptionHandler { _, throwable ->
throw throwable
}
Though this would probably not be suitable for production. Maybe need to catch cancellation exceptions or something, I'm not sure
A custom test rule so far seems to be the best solution.
/**
* Coroutines can throw exceptions that can go unnoticed by the JUnit Test Runner which will pass
* a test that should have failed. This rule will ensure the test fails, provided that you use the
* [CoroutineContext] provided by [dispatcher].
*/
class CoroutineExceptionRule : TestWatcher(), TestRule {
private val exceptions = Collections.synchronizedList(mutableListOf<Throwable>())
val dispatcher: CoroutineContext
get() = Unconfined + CoroutineExceptionHandler { _, throwable ->
// I want to hook into test lifecycle and fail test immediately here
exceptions.add(throwable)
// this throw will not always fail the test. this does print the stacktrace at least
throw throwable
}
override fun starting(description: Description) {
// exceptions from a previous test execution should not fail this test
exceptions.clear()
}
override fun finished(description: Description) {
// instead of waiting for test to finish to fail it
exceptions.forEach { throw AssertionError(it) }
}
}
I'm hoping to improve it via this post though
UPDATE: just use runBlocking - like Roman suggests.