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