Skip to content

Commit 49f25a5

Browse files
qwwdfsadelizarov
authored andcommitted
Supervisors guide
1 parent 88e72f9 commit 49f25a5

File tree

6 files changed

+288
-1
lines changed

6 files changed

+288
-1
lines changed
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
/*
2+
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
6+
package kotlinx.coroutines.experimental.guide.supervision01
7+
8+
import kotlinx.coroutines.experimental.*
9+
import kotlin.coroutines.experimental.*
10+
11+
fun main(args: Array<String>) = runBlocking {
12+
val supervisor = SupervisorJob()
13+
with(CoroutineScope(coroutineContext + supervisor)) {
14+
// launch the first child -- its exception is ignored for this example (don't do this in practise!)
15+
val firstChild = launch(CoroutineExceptionHandler { _, _ -> }) {
16+
println("First child is failing")
17+
throw AssertionError("First child is cancelled")
18+
}
19+
// launch the second child
20+
val secondChild = launch {
21+
firstChild.join()
22+
// Cancellation of the first child is not propagated to the second child
23+
println("First child is cancelled: ${firstChild.isCancelled}, but second one is still active")
24+
try {
25+
delay(Long.MAX_VALUE)
26+
} finally {
27+
// But cancellation of the supervisor is propagated
28+
println("Second child is cancelled because supervisor is cancelled")
29+
}
30+
}
31+
// wait until the first child fails & completes
32+
firstChild.join()
33+
println("Cancelling supervisor")
34+
supervisor.cancel()
35+
secondChild.join()
36+
}
37+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/*
2+
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
6+
package kotlinx.coroutines.experimental.guide.supervision02
7+
8+
import kotlinx.coroutines.experimental.*
9+
import kotlin.coroutines.experimental.*
10+
11+
fun main(args: Array<String>) = runBlocking {
12+
try {
13+
supervisorScope {
14+
val child = launch {
15+
try {
16+
println("Child is sleeping")
17+
delay(Long.MAX_VALUE)
18+
} finally {
19+
println("Child is cancelled")
20+
}
21+
}
22+
// Give our child a chance to execute and print using yield
23+
yield()
24+
println("Throwing exception from scope")
25+
throw AssertionError()
26+
}
27+
} catch(e: AssertionError) {
28+
println("Caught assertion error")
29+
}
30+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
3+
*/
4+
5+
// This file was automatically generated from coroutines-guide.md by Knit tool. Do not edit.
6+
package kotlinx.coroutines.experimental.guide.supervision03
7+
8+
import kotlinx.coroutines.experimental.*
9+
import kotlin.coroutines.experimental.*
10+
11+
fun main(args: Array<String>) = runBlocking {
12+
val handler = CoroutineExceptionHandler { _, exception ->
13+
println("Caught $exception")
14+
}
15+
supervisorScope {
16+
val child = launch(handler) {
17+
println("Child throws an exception")
18+
throw AssertionError()
19+
}
20+
println("Scope is completing")
21+
}
22+
println("Scope is completed")
23+
}

core/kotlinx-coroutines-core/test/guide/test/ExceptionsGuideTest.kt

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,4 +56,34 @@ class ExceptionsGuideTest {
5656
"Caught original java.io.IOException"
5757
)
5858
}
59+
60+
@Test
61+
fun testKotlinxCoroutinesExperimentalGuideSupervision01() {
62+
test("KotlinxCoroutinesExperimentalGuideSupervision01") { kotlinx.coroutines.experimental.guide.supervision01.main(emptyArray()) }.verifyLines(
63+
"First child is failing",
64+
"First child is cancelled: true, but second one is still active",
65+
"Cancelling supervisor",
66+
"Second child is cancelled because supervisor is cancelled"
67+
)
68+
}
69+
70+
@Test
71+
fun testKotlinxCoroutinesExperimentalGuideSupervision02() {
72+
test("KotlinxCoroutinesExperimentalGuideSupervision02") { kotlinx.coroutines.experimental.guide.supervision02.main(emptyArray()) }.verifyLines(
73+
"Child is sleeping",
74+
"Throwing exception from scope",
75+
"Child is cancelled",
76+
"Caught assertion error"
77+
)
78+
}
79+
80+
@Test
81+
fun testKotlinxCoroutinesExperimentalGuideSupervision03() {
82+
test("KotlinxCoroutinesExperimentalGuideSupervision03") { kotlinx.coroutines.experimental.guide.supervision03.main(emptyArray()) }.verifyLines(
83+
"Scope is completing",
84+
"Child throws an exception",
85+
"Caught java.lang.AssertionError",
86+
"Scope is completed"
87+
)
88+
}
5989
}

docs/coroutines-guide.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ In order to use coroutines as well as follow the examples in this guide, you nee
1919
* [Cancellation and timeouts](cancellation-and-timeouts.md)
2020
* [Composing suspending functions](composing-suspending-functions.md)
2121
* [Coroutine context and dispatchers](coroutine-context-and-dispatchers.md)
22-
* [Exception handling](exception-handling.md)
22+
* [Exception handling and supervision](exception-handling.md)
2323
* [Channels (experimental)](channels.md)
2424
* [Shared mutable state and concurrency](shared-mutable-state-and-concurrency.md)
2525
* [Select expression (experimental)](select-expression.md)

docs/exception-handling.md

Lines changed: 167 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)