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 — 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