Change contract of supervisorScope to align it with coroutineScope
diff --git a/common/kotlinx-coroutines-core-common/src/Supervisor.kt b/common/kotlinx-coroutines-core-common/src/Supervisor.kt
index 2e4fe89..637ed4e 100644
--- a/common/kotlinx-coroutines-core-common/src/Supervisor.kt
+++ b/common/kotlinx-coroutines-core-common/src/Supervisor.kt
@@ -32,8 +32,10 @@
*
* A failure of a child does not cause this scope to fail and does not affect its other children,
* so a custom policy for handling failures of its children can be implemented. See [SupervisorJob] for details.
+ * A failure of the scope itself (exception thrown in the [block] or cancellation) fails the scope with all its children,
+ * but does not cancel parent job.
*/
-public suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R {
+public suspend fun <R> supervisorScope(block: suspend CoroutineScope.() -> R): R {
// todo: optimize implementation to a single allocated object
// todo: fix copy-and-paste with coroutineScope
val owner = SupervisorCoroutine<R>(coroutineContext)
@@ -62,6 +64,6 @@
private class SupervisorCoroutine<R>(
parentContext: CoroutineContext
) : AbstractCoroutine<R>(parentContext, true) {
- override val cancelsParent: Boolean get() = true
+ override val cancelsParent: Boolean get() = false
override fun childCancelled(cause: Throwable): Boolean = false
}
diff --git a/common/kotlinx-coroutines-core-common/test/SupervisorTest.kt b/common/kotlinx-coroutines-core-common/test/SupervisorTest.kt
index 11b939d..02a58ca 100644
--- a/common/kotlinx-coroutines-core-common/test/SupervisorTest.kt
+++ b/common/kotlinx-coroutines-core-common/test/SupervisorTest.kt
@@ -32,6 +32,8 @@
finish(5)
assertTrue(job1.isCancelled)
assertTrue(job2.isCancelled)
+ assertFalse(supervisor.isCancelled)
+ assertFalse(supervisor.isCompleted)
}
@Test
@@ -54,6 +56,57 @@
}
@Test
+ fun testSupervisorScopeIsolation() = runTest(
+ unhandled = listOf(
+ { it -> it is TestException2 })
+ ) {
+ val result = supervisorScope {
+ expect(1)
+ val job = launch {
+ expect(2)
+ delay(Long.MAX_VALUE)
+ }
+
+ val failingJob = launch {
+ expect(3)
+ throw TestException2()
+ }
+
+ failingJob.join()
+ yield()
+ expect(4)
+ assertTrue(job.isActive)
+ assertFalse(job.isCancelled)
+ job.cancel()
+ "OK"
+ }
+ assertEquals("OK", result)
+ finish(5)
+ }
+
+ @Test
+ fun testThrowingSupervisorScope() = runTest {
+ try {
+ expect(1)
+ supervisorScope {
+ async {
+ try {
+ delay(Long.MAX_VALUE)
+ } finally {
+ expect(3)
+ }
+ }
+
+ expect(2)
+ yield()
+ throw TestException2()
+ }
+ } catch (e: Throwable) {
+ finish(4)
+ }
+ }
+
+ @Test
fun testSupervisorWithParentCancelNormally() {
val parent = Job()
val supervisor = SupervisorJob(parent)