Kotlin Help
Coroutine exceptions handling
This section covers exception handling and cancellation on exceptions. We already know that a cancelled coroutine throwsCancellationException in suspension points and that it is ignored by the coroutines' machinery. Here we look at what happens if an exception is thrown during cancellation or multiple children of the same coroutine throw an exception.
Exception propagation
Coroutine builders come in two flavors: propagating exceptions automatically (launch) or exposing them to users (async andproduce). When these builders are used to create aroot coroutine, that is not achild of another coroutine, the former builders treat exceptions asuncaught exceptions, similar to Java'sThread.uncaughtExceptionHandler, while the latter are relying on the user to consume the final exception, for example viaawait orreceive (produce andreceive are covered inChannels section).
It can be demonstrated by a simple example that creates root coroutines using theGlobalScope:
GlobalScope is a delicate API that can backfire in non-trivial ways. Creating a root coroutine for the whole application is one of the rare legitimate uses forGlobalScope, so you must explicitly opt-in into usingGlobalScope with@OptIn(DelicateCoroutinesApi::class).
You can get the full codehere.
The output of this code is (withdebug):
CoroutineExceptionHandler
It is possible to customize the default behavior of printinguncaught exceptions to the console.CoroutineExceptionHandler context element on aroot coroutine can be used as a genericcatch block for this root coroutine and all its children where custom exception handling may take place. It is similar toThread.uncaughtExceptionHandler. You cannot recover from the exception in theCoroutineExceptionHandler. The coroutine had already completed with the corresponding exception when the handler is called. Normally, the handler is used to log the exception, show some kind of error message, terminate, and/or restart the application.
CoroutineExceptionHandler is invoked only onuncaught exceptions — exceptions that were not handled in any other way. In particular, allchildren coroutines (coroutines created in the context of anotherJob) delegate handling of their exceptions to their parent coroutine, which also delegates to the parent, and so on until the root, so theCoroutineExceptionHandler installed in their context is never used. In addition to that,async builder always catches all exceptions and represents them in the resultingDeferred object, so itsCoroutineExceptionHandler has no effect either.
Coroutines running in supervision scope do not propagate exceptions to their parent and are excluded from this rule. A furtherSupervision section of this document gives more details.
You can get the full codehere.
The output of this code is:
Cancellation and exceptions
Cancellation is closely related to exceptions. Coroutines internally useCancellationException for cancellation, these exceptions are ignored by all handlers, so they should be used only as the source of additional debug information, which can be obtained bycatch block. When a coroutine is cancelled usingJob.cancel, it terminates, but it does not cancel its parent.
You can get the full codehere.
The output of this code is:
If a coroutine encounters an exception other thanCancellationException, it cancels its parent with that exception. This behaviour cannot be overridden and is used to provide stable coroutines hierarchies forstructured concurrency.CoroutineExceptionHandler implementation is not used for child coroutines.
In these examples,CoroutineExceptionHandler is always installed to a coroutine that is created inGlobalScope. It does not make sense to install an exception handler to a coroutine that is launched in the scope of the mainrunBlocking, since the main coroutine is going to be always cancelled when its child completes with exception despite the installed handler.
The original exception is handled by the parent only when all its children terminate, which is demonstrated by the following example.
You can get the full codehere.
The output of this code is:
Exceptions aggregation
When multiple children of a coroutine fail with an exception, the general rule is "the first exception wins", so the first exception gets handled. All additional exceptions that happen after the first one are attached to the first exception as suppressed ones.
You can get the full codehere.
The output of this code is:
Note that this mechanism currently only works on Java version 1.7+. The JS and Native restrictions are temporary and will be lifted in the future.
Cancellation exceptions are transparent and are unwrapped by default:
You can get the full codehere.
The output of this code is:
Supervision
As we have studied before, cancellation is a bidirectional relationship propagating through the whole hierarchy of coroutines. Let us take a look at the case when unidirectional cancellation is required.
A good example of such a requirement is a UI component with the job defined in its scope. If any of the UI's child tasks have failed, it is not always necessary to cancel (effectively kill) the whole UI component, but if the UI component is destroyed (and its job is cancelled), then it is necessary to cancel all child jobs as their results are no longer needed.
Another example is a server process that spawns multiple child jobs and needs tosupervise their execution, tracking their failures and only restarting the failed ones.
Supervision job
TheSupervisorJob can be used for these purposes. It is similar to a regularJob with the only exception that cancellation is propagated only downwards. This can easily be demonstrated using the following example:
You can get the full codehere.
The output of this code is:
Supervision scope
Instead ofcoroutineScope, we can usesupervisorScope forscoped concurrency. It propagates the cancellation in one direction only and cancels all its children only if it failed itself. It also waits for all children before completion just likecoroutineScope does.
You can get the full codehere.
The output of this code is:
Exceptions in supervised coroutines
Another crucial difference between regular and supervisor jobs is exception handling. Every child should handle its exceptions by itself via the exception handling mechanism. This difference comes from the fact that child's failure does not propagate to the parent. It means that coroutines launched directly inside thesupervisorScopedo use theCoroutineExceptionHandler that is installed in their scope in the same way as root coroutines do (see theCoroutineExceptionHandler section for details).
You can get the full codehere.
The output of this code is: