Skip to content

Commit 1d04452

Browse files
committed
Revisit SupervisorScope, supervisorScope, and coroutineScope docs
Seems like a lot of the information was outdated. Fixes #3725
1 parent 17bae3f commit 1d04452

File tree

3 files changed

+47
-18
lines changed

3 files changed

+47
-18
lines changed

kotlinx-coroutines-core/common/src/CoroutineScope.kt

+7-7
Original file line numberDiff line numberDiff line change
@@ -220,13 +220,13 @@ public object GlobalScope : CoroutineScope {
220220

221221
/**
222222
* Creates a [CoroutineScope] and calls the specified suspend block with this scope.
223-
* The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, but overrides
224-
* the context's [Job].
223+
* The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, using the
224+
* [Job] from that context as the parent for a new [Job].
225225
*
226226
* This function is designed for _concurrent decomposition_ of work. When any child coroutine in this scope fails,
227-
* this scope fails and all the rest of the children are cancelled (for a different behavior see [supervisorScope]).
228-
* This function returns as soon as the given block and all its children coroutines are completed.
229-
* A usage example of a scope looks like this:
227+
* this scope fails, cancelling all the other children (for a different behavior, see [supervisorScope]).
228+
* This function returns as soon as the given block and all its child coroutines are completed.
229+
* A usage of a scope looks like this:
230230
*
231231
* ```
232232
* suspend fun showSomeData() = coroutineScope {
@@ -248,8 +248,8 @@ public object GlobalScope : CoroutineScope {
248248
* 3) If the outer scope of `showSomeData` is cancelled, both started `async` and `withContext` blocks are cancelled.
249249
* 4) If the `async` block fails, `withContext` will be cancelled.
250250
*
251-
* The method may throw a [CancellationException] if the current job was cancelled externally
252-
* or may throw a corresponding unhandled [Throwable] if there is any unhandled exception in this scope
251+
* The method may throw a [CancellationException] if the current job was cancelled externally,
252+
* rethrow the exception thrown by [block], or throw an unhandled [Throwable] if there is one
253253
* (for example, from a crashed coroutine that was started with [launch][CoroutineScope.launch] in this scope).
254254
*/
255255
public suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R {

kotlinx-coroutines-core/common/src/Supervisor.kt

+8-10
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,8 @@ import kotlin.jvm.*
2020
* - A failure of a child job that was created using [launch][CoroutineScope.launch] can be handled via [CoroutineExceptionHandler] in the context.
2121
* - A failure of a child job that was created using [async][CoroutineScope.async] can be handled via [Deferred.await] on the resulting deferred value.
2222
*
23-
* If [parent] job is specified, then this supervisor job becomes a child job of its parent and is cancelled when its
24-
* parent fails or is cancelled. All this supervisor's children are cancelled in this case, too. The invocation of
25-
* [cancel][Job.cancel] with exception (other than [CancellationException]) on this supervisor job also cancels parent.
26-
*
27-
* @param parent an optional parent job.
23+
* If a [parent] job is specified, then this supervisor job becomes a child job of [parent] and is cancelled when the
24+
* parent fails or is cancelled. All this supervisor's children are cancelled in this case, too.
2825
*/
2926
@Suppress("FunctionName")
3027
public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)
@@ -36,15 +33,16 @@ public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobIm
3633
public fun SupervisorJob0(parent: Job? = null) : Job = SupervisorJob(parent)
3734

3835
/**
39-
* Creates a [CoroutineScope] with [SupervisorJob] and calls the specified suspend block with this scope.
40-
* The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, but overrides
41-
* context's [Job] with [SupervisorJob].
36+
* Creates a [CoroutineScope] with [SupervisorJob] and calls the specified suspend [block] with this scope.
37+
* The provided scope inherits its [coroutineContext][CoroutineScope.coroutineContext] from the outer scope, using the
38+
* [Job] from that context as the parent for the new [SupervisorJob].
4239
* This function returns as soon as the given block and all its child coroutines are completed.
4340
*
4441
* Unlike [coroutineScope], a failure of a child does not cause this scope to fail and does not affect its other children,
4542
* so a custom policy for handling failures of its children can be implemented. See [SupervisorJob] for additional details.
46-
* A failure of the scope itself (exception thrown in the [block] or external cancellation) fails the scope with all its children,
47-
* but does not cancel parent job.
43+
*
44+
* If an exception happened in [block], then the supervisor job is failed and all its children are cancelled.
45+
* If the current coroutine was cancelled, then both the supervisor job itself and all its children are cancelled.
4846
*
4947
* The method may throw a [CancellationException] if the current job was cancelled externally,
5048
* or rethrow an exception thrown by the given [block].

kotlinx-coroutines-core/common/test/SupervisorTest.kt

+32-1
Original file line numberDiff line numberDiff line change
@@ -83,10 +83,12 @@ class SupervisorTest : TestBase() {
8383

8484
@Test
8585
fun testThrowingSupervisorScope() = runTest {
86+
var childJob: Job? = null
87+
var supervisorJob: Job? = null
8688
try {
8789
expect(1)
8890
supervisorScope {
89-
async {
91+
childJob = async {
9092
try {
9193
delay(Long.MAX_VALUE)
9294
} finally {
@@ -96,9 +98,13 @@ class SupervisorTest : TestBase() {
9698

9799
expect(2)
98100
yield()
101+
supervisorJob = coroutineContext.job
99102
throw TestException2()
100103
}
101104
} catch (e: Throwable) {
105+
assertIs<TestException2>(e)
106+
assertTrue(childJob!!.isCancelled)
107+
assertTrue(supervisorJob!!.isCancelled)
102108
finish(4)
103109
}
104110
}
@@ -155,6 +161,31 @@ class SupervisorTest : TestBase() {
155161
}
156162
}
157163

164+
/**
165+
* Tests that [supervisorScope] cancels all its children when the current coroutine is cancelled.
166+
*/
167+
@Test
168+
fun testSupervisorScopeExternalCancellation() = runTest {
169+
var childJob: Job? = null
170+
val job = launch {
171+
supervisorScope {
172+
childJob = launch(start = CoroutineStart.UNDISPATCHED) {
173+
try {
174+
delay(Long.MAX_VALUE)
175+
} finally {
176+
expect(2)
177+
}
178+
}
179+
}
180+
}
181+
while (childJob == null) yield()
182+
expect(1)
183+
job.cancel()
184+
assertTrue(childJob!!.isCancelled)
185+
job.join()
186+
finish(3)
187+
}
188+
158189
@Test
159190
fun testAsyncCancellation() = runTest {
160191
val parent = SupervisorJob()

0 commit comments

Comments
 (0)