• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1<!--- TEST_NAME ExceptionsGuideTest -->
2
3**Table of contents**
4
5<!--- TOC -->
6
7* [Exception Handling](#exception-handling)
8  * [Exception propagation](#exception-propagation)
9  * [CoroutineExceptionHandler](#coroutineexceptionhandler)
10  * [Cancellation and exceptions](#cancellation-and-exceptions)
11  * [Exceptions aggregation](#exceptions-aggregation)
12  * [Supervision](#supervision)
13    * [Supervision job](#supervision-job)
14    * [Supervision scope](#supervision-scope)
15    * [Exceptions in supervised coroutines](#exceptions-in-supervised-coroutines)
16
17<!--- END -->
18
19## Exception Handling
20
21This section covers exception handling and cancellation on exceptions.
22We already know that a cancelled coroutine throws [CancellationException] in suspension points and that it
23is ignored by the coroutines' machinery. Here we look at what happens if an exception is thrown during cancellation or multiple children of the same
24coroutine throw an exception.
25
26### Exception propagation
27
28Coroutine builders come in two flavors: propagating exceptions automatically ([launch] and [actor]) or
29exposing them to users ([async] and [produce]).
30When these builders are used to create a _root_ coroutine, that is not a _child_ of another coroutine,
31the former builders treat exceptions as **uncaught** exceptions, similar to Java's `Thread.uncaughtExceptionHandler`,
32while the latter are relying on the user to consume the final
33exception, for example via [await][Deferred.await] or [receive][ReceiveChannel.receive]
34([produce] and [receive][ReceiveChannel.receive] are covered later in [Channels](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/channels.md) section).
35
36It can be demonstrated by a simple example that creates root coroutines using the [GlobalScope]:
37
38<div class="sample" markdown="1" theme="idea" data-highlight-only>
39
40```kotlin
41import kotlinx.coroutines.*
42
43fun main() = runBlocking {
44    val job = GlobalScope.launch { // root coroutine with launch
45        println("Throwing exception from launch")
46        throw IndexOutOfBoundsException() // Will be printed to the console by Thread.defaultUncaughtExceptionHandler
47    }
48    job.join()
49    println("Joined failed job")
50    val deferred = GlobalScope.async { // root coroutine with async
51        println("Throwing exception from async")
52        throw ArithmeticException() // Nothing is printed, relying on user to call await
53    }
54    try {
55        deferred.await()
56        println("Unreached")
57    } catch (e: ArithmeticException) {
58        println("Caught ArithmeticException")
59    }
60}
61```
62
63</div>
64
65> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-01.kt).
66
67The output of this code is (with [debug](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/coroutine-context-and-dispatchers.md#debugging-coroutines-and-threads)):
68
69```text
70Throwing exception from launch
71Exception in thread "DefaultDispatcher-worker-2 @coroutine#2" java.lang.IndexOutOfBoundsException
72Joined failed job
73Throwing exception from async
74Caught ArithmeticException
75```
76
77<!--- TEST EXCEPTION-->
78
79### CoroutineExceptionHandler
80
81It is possible to customize the default behavior of printing **uncaught** exceptions to the console.
82[CoroutineExceptionHandler] context element on a _root_ coroutine can be used as generic `catch` block for
83this root coroutine and all its children where custom exception handling may take place.
84It is similar to [`Thread.uncaughtExceptionHandler`](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler)).
85You cannot recover from the exception in the `CoroutineExceptionHandler`. The coroutine had already completed
86with the corresponding exception when the handler is called. Normally, the handler is used to
87log the exception, show some kind of error message, terminate, and/or restart the application.
88
89On JVM it is possible to redefine global exception handler for all coroutines by registering [CoroutineExceptionHandler] via
90[`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html).
91Global exception handler is similar to
92[`Thread.defaultUncaughtExceptionHandler`](https://docs.oracle.com/javase/8/docs/api/java/lang/Thread.html#setDefaultUncaughtExceptionHandler(java.lang.Thread.UncaughtExceptionHandler))
93which is used when no more specific handlers are registered.
94On Android, `uncaughtExceptionPreHandler` is installed as a global coroutine exception handler.
95
96`CoroutineExceptionHandler` is invoked only on **uncaught** exceptions &mdash; exceptions that were not handled in any other way.
97In particular, all _children_ coroutines (coroutines created in the context of another [Job]) delegate handling of
98their exceptions to their parent coroutine, which also delegates to the parent, and so on until the root,
99so the `CoroutineExceptionHandler` installed in their context is never used.
100In addition to that, [async] builder always catches all exceptions and represents them in the resulting [Deferred] object,
101so its `CoroutineExceptionHandler` has no effect either.
102
103> Coroutines running in supervision scope do not propagate exceptions to their parent and are
104excluded from this rule. A further [Supervision](#supervision) section of this document gives more details.
105
106<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
107
108```kotlin
109import kotlinx.coroutines.*
110
111fun main() = runBlocking {
112//sampleStart
113    val handler = CoroutineExceptionHandler { _, exception ->
114        println("CoroutineExceptionHandler got $exception")
115    }
116    val job = GlobalScope.launch(handler) { // root coroutine, running in GlobalScope
117        throw AssertionError()
118    }
119    val deferred = GlobalScope.async(handler) { // also root, but async instead of launch
120        throw ArithmeticException() // Nothing will be printed, relying on user to call deferred.await()
121    }
122    joinAll(job, deferred)
123//sampleEnd
124}
125```
126
127</div>
128
129> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-02.kt).
130
131The output of this code is:
132
133```text
134CoroutineExceptionHandler got java.lang.AssertionError
135```
136
137<!--- TEST-->
138
139### Cancellation and exceptions
140
141Cancellation is closely related to exceptions. Coroutines internally use `CancellationException` for cancellation, these
142exceptions are ignored by all handlers, so they should be used only as the source of additional debug information, which can
143be obtained by `catch` block.
144When a coroutine is cancelled using [Job.cancel], it terminates, but it does not cancel its parent.
145
146<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
147
148```kotlin
149import kotlinx.coroutines.*
150
151fun main() = runBlocking {
152//sampleStart
153    val job = launch {
154        val child = launch {
155            try {
156                delay(Long.MAX_VALUE)
157            } finally {
158                println("Child is cancelled")
159            }
160        }
161        yield()
162        println("Cancelling child")
163        child.cancel()
164        child.join()
165        yield()
166        println("Parent is not cancelled")
167    }
168    job.join()
169//sampleEnd
170}
171```
172
173</div>
174
175> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-03.kt).
176
177The output of this code is:
178
179```text
180Cancelling child
181Child is cancelled
182Parent is not cancelled
183```
184
185<!--- TEST-->
186
187If a coroutine encounters an exception other than `CancellationException`, it cancels its parent with that exception.
188This behaviour cannot be overridden and is used to provide stable coroutines hierarchies for
189[structured concurrency](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/composing-suspending-functions.md#structured-concurrency-with-async).
190[CoroutineExceptionHandler] implementation is not used for child coroutines.
191
192> In these examples [CoroutineExceptionHandler] is always installed to a coroutine
193that is created in [GlobalScope]. It does not make sense to install an exception handler to a coroutine that
194is launched in the scope of the main [runBlocking], since the main coroutine is going to be always cancelled
195when its child completes with exception despite the installed handler.
196
197The original exception is handled by the parent only when all its children terminate,
198which is demonstrated by the following example.
199
200<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
201
202```kotlin
203import kotlinx.coroutines.*
204
205fun main() = runBlocking {
206//sampleStart
207    val handler = CoroutineExceptionHandler { _, exception ->
208        println("CoroutineExceptionHandler got $exception")
209    }
210    val job = GlobalScope.launch(handler) {
211        launch { // the first child
212            try {
213                delay(Long.MAX_VALUE)
214            } finally {
215                withContext(NonCancellable) {
216                    println("Children are cancelled, but exception is not handled until all children terminate")
217                    delay(100)
218                    println("The first child finished its non cancellable block")
219                }
220            }
221        }
222        launch { // the second child
223            delay(10)
224            println("Second child throws an exception")
225            throw ArithmeticException()
226        }
227    }
228    job.join()
229//sampleEnd
230}
231```
232
233</div>
234
235> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-04.kt).
236
237The output of this code is:
238
239```text
240Second child throws an exception
241Children are cancelled, but exception is not handled until all children terminate
242The first child finished its non cancellable block
243CoroutineExceptionHandler got java.lang.ArithmeticException
244```
245<!--- TEST-->
246
247### Exceptions aggregation
248
249When multiple children of a coroutine fail with an exception, the
250general rule is "the first exception wins", so the first exception gets handled.
251All additional exceptions that happen after the first one are attached to the first exception as suppressed ones.
252
253<!--- INCLUDE
254import kotlinx.coroutines.exceptions.*
255-->
256
257<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
258
259```kotlin
260import kotlinx.coroutines.*
261import java.io.*
262
263fun main() = runBlocking {
264    val handler = CoroutineExceptionHandler { _, exception ->
265        println("CoroutineExceptionHandler got $exception with suppressed ${exception.suppressed.contentToString()}")
266    }
267    val job = GlobalScope.launch(handler) {
268        launch {
269            try {
270                delay(Long.MAX_VALUE) // it gets cancelled when another sibling fails with IOException
271            } finally {
272                throw ArithmeticException() // the second exception
273            }
274        }
275        launch {
276            delay(100)
277            throw IOException() // the first exception
278        }
279        delay(Long.MAX_VALUE)
280    }
281    job.join()
282}
283```
284
285</div>
286
287> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-05.kt).
288
289> Note: This above code will work properly only on JDK7+ that supports `suppressed` exceptions
290
291The output of this code is:
292
293```text
294CoroutineExceptionHandler got java.io.IOException with suppressed [java.lang.ArithmeticException]
295```
296
297<!--- TEST-->
298
299> Note that this mechanism currently only works on Java version 1.7+.
300The JS and Native restrictions are temporary and will be lifted in the future.
301
302Cancellation exceptions are transparent and are unwrapped by default:
303
304<div class="sample" markdown="1" theme="idea" data-min-compiler-version="1.3">
305
306```kotlin
307import kotlinx.coroutines.*
308import java.io.*
309
310fun main() = runBlocking {
311//sampleStart
312    val handler = CoroutineExceptionHandler { _, exception ->
313        println("CoroutineExceptionHandler got $exception")
314    }
315    val job = GlobalScope.launch(handler) {
316        val inner = launch { // all this stack of coroutines will get cancelled
317            launch {
318                launch {
319                    throw IOException() // the original exception
320                }
321            }
322        }
323        try {
324            inner.join()
325        } catch (e: CancellationException) {
326            println("Rethrowing CancellationException with original cause")
327            throw e // cancellation exception is rethrown, yet the original IOException gets to the handler
328        }
329    }
330    job.join()
331//sampleEnd
332}
333```
334
335</div>
336
337> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt).
338
339The output of this code is:
340
341```text
342Rethrowing CancellationException with original cause
343CoroutineExceptionHandler got java.io.IOException
344```
345<!--- TEST-->
346
347### Supervision
348
349As we have studied before, cancellation is a bidirectional relationship propagating through the whole
350hierarchy of coroutines. Let us take a look at the case when unidirectional cancellation is required.
351
352A 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
353have failed, it is not always necessary to cancel (effectively kill) the whole UI component,
354but if UI component is destroyed (and its job is cancelled), then it is necessary to fail all child jobs as their results are no longer needed.
355
356Another example is a server process that spawns multiple child jobs and needs to _supervise_
357their execution, tracking their failures and only restarting the failed ones.
358
359#### Supervision job
360
361The [SupervisorJob][SupervisorJob()] can be used for these purposes.
362It is similar to a regular [Job][Job()] with the only exception that cancellation is propagated
363only downwards. This can easily be demonstrated using the following example:
364
365<div class="sample" markdown="1" theme="idea" data-highlight-only>
366
367```kotlin
368import kotlinx.coroutines.*
369
370fun main() = runBlocking {
371    val supervisor = SupervisorJob()
372    with(CoroutineScope(coroutineContext + supervisor)) {
373        // launch the first child -- its exception is ignored for this example (don't do this in practice!)
374        val firstChild = launch(CoroutineExceptionHandler { _, _ ->  }) {
375            println("The first child is failing")
376            throw AssertionError("The first child is cancelled")
377        }
378        // launch the second child
379        val secondChild = launch {
380            firstChild.join()
381            // Cancellation of the first child is not propagated to the second child
382            println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
383            try {
384                delay(Long.MAX_VALUE)
385            } finally {
386                // But cancellation of the supervisor is propagated
387                println("The second child is cancelled because the supervisor was cancelled")
388            }
389        }
390        // wait until the first child fails & completes
391        firstChild.join()
392        println("Cancelling the supervisor")
393        supervisor.cancel()
394        secondChild.join()
395    }
396}
397```
398
399</div>
400
401> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt).
402
403The output of this code is:
404
405```text
406The first child is failing
407The first child is cancelled: true, but the second one is still active
408Cancelling the supervisor
409The second child is cancelled because the supervisor was cancelled
410```
411<!--- TEST-->
412
413
414#### Supervision scope
415
416Instead of [coroutineScope][_coroutineScope], we can use [supervisorScope][_supervisorScope] for _scoped_ concurrency. It propagates the cancellation
417in one direction only and cancels all its children only if it failed itself. It also waits for all children before completion
418just like [coroutineScope][_coroutineScope] does.
419
420<div class="sample" markdown="1" theme="idea" data-highlight-only>
421
422```kotlin
423import kotlin.coroutines.*
424import kotlinx.coroutines.*
425
426fun main() = runBlocking {
427    try {
428        supervisorScope {
429            val child = launch {
430                try {
431                    println("The child is sleeping")
432                    delay(Long.MAX_VALUE)
433                } finally {
434                    println("The child is cancelled")
435                }
436            }
437            // Give our child a chance to execute and print using yield
438            yield()
439            println("Throwing an exception from the scope")
440            throw AssertionError()
441        }
442    } catch(e: AssertionError) {
443        println("Caught an assertion error")
444    }
445}
446```
447
448</div>
449
450> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt).
451
452The output of this code is:
453
454```text
455The child is sleeping
456Throwing an exception from the scope
457The child is cancelled
458Caught an assertion error
459```
460<!--- TEST-->
461
462#### Exceptions in supervised coroutines
463
464Another crucial difference between regular and supervisor jobs is exception handling.
465Every child should handle its exceptions by itself via the exception handling mechanism.
466This difference comes from the fact that child's failure does not propagate to the parent.
467It means that coroutines launched directly inside the [supervisorScope][_supervisorScope] _do_ use the [CoroutineExceptionHandler]
468that is installed in their scope in the same way as root coroutines do
469(see the [CoroutineExceptionHandler](#coroutineexceptionhandler) section for details).
470
471<div class="sample" markdown="1" theme="idea" data-highlight-only>
472
473```kotlin
474import kotlin.coroutines.*
475import kotlinx.coroutines.*
476
477fun main() = runBlocking {
478    val handler = CoroutineExceptionHandler { _, exception ->
479        println("CoroutineExceptionHandler got $exception")
480    }
481    supervisorScope {
482        val child = launch(handler) {
483            println("The child throws an exception")
484            throw AssertionError()
485        }
486        println("The scope is completing")
487    }
488    println("The scope is completed")
489}
490```
491
492</div>
493
494> You can get the full code [here](../kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt).
495
496The output of this code is:
497
498```text
499The scope is completing
500The child throws an exception
501CoroutineExceptionHandler got java.lang.AssertionError
502The scope is completed
503```
504<!--- TEST-->
505
506<!--- MODULE kotlinx-coroutines-core -->
507<!--- INDEX kotlinx.coroutines -->
508[CancellationException]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
509[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
510[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
511[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
512[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
513[CoroutineExceptionHandler]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
514[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
515[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
516[Job.cancel]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/cancel.html
517[runBlocking]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
518[SupervisorJob()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html
519[Job()]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job.html
520[_coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
521[_supervisorScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/supervisor-scope.html
522<!--- INDEX kotlinx.coroutines.channels -->
523[actor]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/actor.html
524[produce]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
525[ReceiveChannel.receive]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
526<!--- END -->
527