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