xref: /aosp_15_r20/external/kotlinx.coroutines/docs/topics/exception-handling.md (revision 7a7160fed73afa6648ef8aa100d4a336fe921d9a)
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]) 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-1 @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
279The output of this code is:
280
281```text
282CoroutineExceptionHandler got java.io.IOException with suppressed [java.lang.ArithmeticException]
283```
284
285<!--- TEST-->
286
287> Note that this mechanism currently only works on Java version 1.7+.
288> The JS and Native restrictions are temporary and will be lifted in the future.
289>
290{type="note"}
291
292Cancellation exceptions are transparent and are unwrapped by default:
293
294```kotlin
295import kotlinx.coroutines.*
296import java.io.*
297
298@OptIn(DelicateCoroutinesApi::class)
299fun main() = runBlocking {
300//sampleStart
301    val handler = CoroutineExceptionHandler { _, exception ->
302        println("CoroutineExceptionHandler got $exception")
303    }
304    val job = GlobalScope.launch(handler) {
305        val innerJob = launch { // all this stack of coroutines will get cancelled
306            launch {
307                launch {
308                    throw IOException() // the original exception
309                }
310            }
311        }
312        try {
313            innerJob.join()
314        } catch (e: CancellationException) {
315            println("Rethrowing CancellationException with original cause")
316            throw e // cancellation exception is rethrown, yet the original IOException gets to the handler
317        }
318    }
319    job.join()
320//sampleEnd
321}
322```
323{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
324
325> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-exceptions-06.kt).
326>
327{type="note"}
328
329The output of this code is:
330
331```text
332Rethrowing CancellationException with original cause
333CoroutineExceptionHandler got java.io.IOException
334```
335
336<!--- TEST-->
337
338## Supervision
339
340As we have studied before, cancellation is a bidirectional relationship propagating through the whole
341hierarchy of coroutines. Let us take a look at the case when unidirectional cancellation is required.
342
343A 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
344have failed, it is not always necessary to cancel (effectively kill) the whole UI component,
345but 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.
346
347Another example is a server process that spawns multiple child jobs and needs to _supervise_
348their execution, tracking their failures and only restarting the failed ones.
349
350### Supervision job
351
352The [SupervisorJob][SupervisorJob()] can be used for these purposes.
353It is similar to a regular [Job][Job()] with the only exception that cancellation is propagated
354only downwards. This can easily be demonstrated using the following example:
355
356```kotlin
357import kotlinx.coroutines.*
358
359fun main() = runBlocking {
360//sampleStart
361    val supervisor = SupervisorJob()
362    with(CoroutineScope(coroutineContext + supervisor)) {
363        // launch the first child -- its exception is ignored for this example (don't do this in practice!)
364        val firstChild = launch(CoroutineExceptionHandler { _, _ ->  }) {
365            println("The first child is failing")
366            throw AssertionError("The first child is cancelled")
367        }
368        // launch the second child
369        val secondChild = launch {
370            firstChild.join()
371            // Cancellation of the first child is not propagated to the second child
372            println("The first child is cancelled: ${firstChild.isCancelled}, but the second one is still active")
373            try {
374                delay(Long.MAX_VALUE)
375            } finally {
376                // But cancellation of the supervisor is propagated
377                println("The second child is cancelled because the supervisor was cancelled")
378            }
379        }
380        // wait until the first child fails & completes
381        firstChild.join()
382        println("Cancelling the supervisor")
383        supervisor.cancel()
384        secondChild.join()
385    }
386//sampleEnd
387}
388```
389{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
390
391> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-supervision-01.kt).
392>
393{type="note"}
394
395The output of this code is:
396
397```text
398The first child is failing
399The first child is cancelled: true, but the second one is still active
400Cancelling the supervisor
401The second child is cancelled because the supervisor was cancelled
402```
403
404<!--- TEST-->
405
406### Supervision scope
407
408Instead of [coroutineScope][_coroutineScope], we can use [supervisorScope][_supervisorScope] for _scoped_ concurrency. It propagates the cancellation
409in one direction only and cancels all its children only if it failed itself. It also waits for all children before completion
410just like [coroutineScope][_coroutineScope] does.
411
412```kotlin
413import kotlin.coroutines.*
414import kotlinx.coroutines.*
415
416fun main() = runBlocking {
417//sampleStart
418    try {
419        supervisorScope {
420            val child = launch {
421                try {
422                    println("The child is sleeping")
423                    delay(Long.MAX_VALUE)
424                } finally {
425                    println("The child is cancelled")
426                }
427            }
428            // Give our child a chance to execute and print using yield
429            yield()
430            println("Throwing an exception from the scope")
431            throw AssertionError()
432        }
433    } catch(e: AssertionError) {
434        println("Caught an assertion error")
435    }
436//sampleEnd
437}
438```
439{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
440
441> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-supervision-02.kt).
442>
443{type="note"}
444
445The output of this code is:
446
447```text
448The child is sleeping
449Throwing an exception from the scope
450The child is cancelled
451Caught an assertion error
452```
453
454<!--- TEST-->
455
456#### Exceptions in supervised coroutines
457
458Another crucial difference between regular and supervisor jobs is exception handling.
459Every child should handle its exceptions by itself via the exception handling mechanism.
460This difference comes from the fact that child's failure does not propagate to the parent.
461It means that coroutines launched directly inside the [supervisorScope][_supervisorScope] _do_ use the [CoroutineExceptionHandler]
462that is installed in their scope in the same way as root coroutines do
463(see the [CoroutineExceptionHandler](#coroutineexceptionhandler) section for details).
464
465```kotlin
466import kotlin.coroutines.*
467import kotlinx.coroutines.*
468
469fun main() = runBlocking {
470//sampleStart
471    val handler = CoroutineExceptionHandler { _, exception ->
472        println("CoroutineExceptionHandler got $exception")
473    }
474    supervisorScope {
475        val child = launch(handler) {
476            println("The child throws an exception")
477            throw AssertionError()
478        }
479        println("The scope is completing")
480    }
481    println("The scope is completed")
482//sampleEnd
483}
484```
485{kotlin-runnable="true" kotlin-min-compiler-version="1.3"}
486
487> You can get the full code [here](../../kotlinx-coroutines-core/jvm/test/guide/example-supervision-03.kt).
488>
489{type="note"}
490
491The output of this code is:
492
493```text
494The scope is completing
495The child throws an exception
496CoroutineExceptionHandler got java.lang.AssertionError
497The scope is completed
498```
499
500<!--- TEST-->
501
502<!--- MODULE kotlinx-coroutines-core -->
503<!--- INDEX kotlinx.coroutines -->
504
505[CancellationException]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-cancellation-exception/index.html
506[launch]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
507[async]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
508[Deferred.await]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
509[GlobalScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
510[CoroutineExceptionHandler]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-exception-handler/index.html
511[Job]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
512[Deferred]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
513[Job.cancel]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/cancel.html
514[runBlocking]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/run-blocking.html
515[SupervisorJob()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-supervisor-job.html
516[Job()]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job.html
517[_coroutineScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
518[_supervisorScope]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/supervisor-scope.html
519
520<!--- INDEX kotlinx.coroutines.channels -->
521
522[produce]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/produce.html
523[ReceiveChannel.receive]: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.channels/-receive-channel/receive.html
524
525<!--- END -->
526