@@ -26,6 +26,10 @@ class ExceptionsGuideTest {
26
26
* [ CoroutineExceptionHandler] ( #coroutineexceptionhandler )
27
27
* [ Cancellation and exceptions] ( #cancellation-and-exceptions )
28
28
* [ Exceptions aggregation] ( #exceptions-aggregation )
29
+ * [ Supervision] ( #supervision )
30
+ * [ Supervision job] ( #supervision-job )
31
+ * [ Supervision scope] ( #supervision-scope )
32
+ * [ Exceptions in supervised coroutines] ( #exceptions-in-supervised-coroutines )
29
33
30
34
<!-- - END_TOC -->
31
35
@@ -344,6 +348,165 @@ Caught original java.io.IOException
344
348
```
345
349
<!-- - TEST-->
346
350
351
+ ## Supervision
352
+
353
+ As we have studied before, cancellation is a bidirectional relationship propagating through the whole
354
+ coroutines hierarchy. But what if unidirectional cancellation is required?
355
+
356
+ Good example of such requirement can be a UI component with the job defined in its scope. If any of UI's child task
357
+ has failed, it is not always necessary to cancel (effectively kill) the whole UI component,
358
+ but if UI component is destroyed (and its job is cancelled), then it is necessary to fail all children jobs as their result is no longer required.
359
+
360
+ Another example is a server process that spawns several children jobs and needs to _ supervise_
361
+ their execution, tracking their failures and restarting just those children jobs that had failed.
362
+
363
+ ### Supervision job
364
+
365
+ For these purposes [ SupervisorJob] [ SupervisorJob() ] can be used. It is similar to a regular [ Job] [ Job() ] with the only exception that cancellation is propagated
366
+ only downwards. It is easy to demonstrate with an example:
367
+
368
+ <!-- - INCLUDE
369
+ import kotlin.coroutines.experimental.*
370
+ -->
371
+
372
+ <div class =" sample " markdown =" 1 " theme =" idea " data-highlight-only >
373
+
374
+ ``` kotlin
375
+ fun main (args : Array <String >) = runBlocking {
376
+ val supervisor = SupervisorJob ()
377
+ with (CoroutineScope (coroutineContext + supervisor)) {
378
+ // launch the first child -- its exception is ignored for this example (don't do this in practise!)
379
+ val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
380
+ println (" First child is failing" )
381
+ throw AssertionError (" First child is cancelled" )
382
+ }
383
+ // launch the second child
384
+ val secondChild = launch {
385
+ firstChild.join()
386
+ // Cancellation of the first child is not propagated to the second child
387
+ println (" First child is cancelled: ${firstChild.isCancelled} , but second one is still active" )
388
+ try {
389
+ delay(Long .MAX_VALUE )
390
+ } finally {
391
+ // But cancellation of the supervisor is propagated
392
+ println (" Second child is cancelled because supervisor is cancelled" )
393
+ }
394
+ }
395
+ // wait until the first child fails & completes
396
+ firstChild.join()
397
+ println (" Cancelling supervisor" )
398
+ supervisor.cancel()
399
+ secondChild.join()
400
+ }
401
+ }
402
+ ```
403
+
404
+ </div >
405
+
406
+ > You can get full code [ here] ( ../core/kotlinx-coroutines-core/test/guide/example-supervision-01.kt )
407
+
408
+ The output of this code is:
409
+
410
+ ``` text
411
+ First child is failing
412
+ First child is cancelled: true, but second one is still active
413
+ Cancelling supervisor
414
+ Second child is cancelled because supervisor is cancelled
415
+ ```
416
+ <!-- - TEST-->
417
+
418
+
419
+ ### Supervision scope
420
+
421
+ For * scoped* concurrency [ supervisorScope] can be used instead of [ coroutineScope] for the same purpose. It propagates cancellation
422
+ only in one direction and cancels all children only if it has failed itself. It also waits for all children before completion
423
+ just like [ coroutineScope] does.
424
+
425
+ <!-- - INCLUDE
426
+ import kotlin.coroutines.experimental.*
427
+ -->
428
+
429
+ <div class =" sample " markdown =" 1 " theme =" idea " data-highlight-only >
430
+
431
+ ``` kotlin
432
+ fun main (args : Array <String >) = runBlocking {
433
+ try {
434
+ supervisorScope {
435
+ val child = launch {
436
+ try {
437
+ println (" Child is sleeping" )
438
+ delay(Long .MAX_VALUE )
439
+ } finally {
440
+ println (" Child is cancelled" )
441
+ }
442
+ }
443
+ // Give our child a chance to execute and print using yield
444
+ yield ()
445
+ println (" Throwing exception from scope" )
446
+ throw AssertionError ()
447
+ }
448
+ } catch (e: AssertionError ) {
449
+ println (" Caught assertion error" )
450
+ }
451
+ }
452
+ ```
453
+
454
+ </div >
455
+
456
+ > You can get full code [ here] ( ../core/kotlinx-coroutines-core/test/guide/example-supervision-02.kt )
457
+
458
+ The output of this code is:
459
+
460
+ ``` text
461
+ Child is sleeping
462
+ Throwing exception from scope
463
+ Child is cancelled
464
+ Caught assertion error
465
+ ```
466
+ <!-- - TEST-->
467
+
468
+ ### Exceptions in supervised coroutines
469
+
470
+ Another crucial difference between regular and supervisor jobs is exception handling.
471
+ Every child should handle its exceptions by itself via exception handling mechanisms.
472
+ This difference comes from the fact that child's failure is not propagated to the parent.
473
+
474
+ <!-- - INCLUDE
475
+ import kotlin.coroutines.experimental.*
476
+ -->
477
+
478
+ <div class =" sample " markdown =" 1 " theme =" idea " data-highlight-only >
479
+
480
+ ``` kotlin
481
+ fun main (args : Array <String >) = runBlocking {
482
+ val handler = CoroutineExceptionHandler { _, exception ->
483
+ println (" Caught $exception " )
484
+ }
485
+ supervisorScope {
486
+ val child = launch(handler) {
487
+ println (" Child throws an exception" )
488
+ throw AssertionError ()
489
+ }
490
+ println (" Scope is completing" )
491
+ }
492
+ println (" Scope is completed" )
493
+ }
494
+ ```
495
+
496
+ </div >
497
+
498
+ > You can get full code [ here] ( ../core/kotlinx-coroutines-core/test/guide/example-supervision-03.kt )
499
+
500
+ The output of this code is:
501
+
502
+ ``` text
503
+ Scope is completing
504
+ Child throws an exception
505
+ Caught java.lang.AssertionError
506
+ Scope is completed
507
+ ```
508
+ <!-- - TEST-->
509
+
347
510
<!-- - MODULE kotlinx-coroutines-core -->
348
511
<!-- - INDEX kotlinx.coroutines.experimental -->
349
512
[ CancellationException ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-cancellation-exception/index.html
@@ -353,6 +516,10 @@ Caught original java.io.IOException
353
516
[ async ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/async.html
354
517
[ Job.cancel ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/cancel.html
355
518
[ runBlocking ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/run-blocking.html
519
+ [ SupervisorJob() ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-supervisor-job.html
520
+ [ Job() ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job.html
521
+ [ supervisorScope ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/supervisor-scope.html
522
+ [ coroutineScope ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/coroutine-scope.html
356
523
<!-- - INDEX kotlinx.coroutines.experimental.channels -->
357
524
[ actor ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/actor.html
358
525
[ produce ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/produce.html
0 commit comments