@@ -26,6 +26,10 @@ class ExceptionsGuideTest {
2626 * [ CoroutineExceptionHandler] ( #coroutineexceptionhandler )
2727 * [ Cancellation and exceptions] ( #cancellation-and-exceptions )
2828 * [ 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 )
2933
3034<!-- - END_TOC -->
3135
@@ -344,6 +348,165 @@ Caught original java.io.IOException
344348```
345349<!-- - TEST-->
346350
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+
347510<!-- - MODULE kotlinx-coroutines-core -->
348511<!-- - INDEX kotlinx.coroutines.experimental -->
349512[ 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
353516[ async ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/async.html
354517[ Job.cancel ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-job/cancel.html
355518[ 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
356523<!-- - INDEX kotlinx.coroutines.experimental.channels -->
357524[ actor ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/actor.html
358525[ produce ] : https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental.channels/produce.html
0 commit comments