diff --git a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt index 9596fe3bea..267cc29d77 100644 --- a/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt +++ b/binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt @@ -483,6 +483,12 @@ public final class kotlinx/coroutines/experimental/ScheduledKt { public static synthetic fun withTimeoutOrNull$default (JLjava/util/concurrent/TimeUnit;Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/experimental/Continuation;ILjava/lang/Object;)Ljava/lang/Object; } +public final class kotlinx/coroutines/experimental/SupervisorKt { + public static final fun SupervisorJob (Lkotlinx/coroutines/experimental/Job;)Lkotlinx/coroutines/experimental/Job; + public static synthetic fun SupervisorJob$default (Lkotlinx/coroutines/experimental/Job;ILjava/lang/Object;)Lkotlinx/coroutines/experimental/Job; + public static final fun supervisorScope (Lkotlin/jvm/functions/Function2;Lkotlin/coroutines/experimental/Continuation;)Ljava/lang/Object; +} + public abstract interface class kotlinx/coroutines/experimental/ThreadContextElement : kotlin/coroutines/experimental/CoroutineContext$Element { public abstract fun restoreThreadContext (Lkotlin/coroutines/experimental/CoroutineContext;Ljava/lang/Object;)V public abstract fun updateThreadContext (Lkotlin/coroutines/experimental/CoroutineContext;)Ljava/lang/Object; diff --git a/common/kotlinx-coroutines-core-common/src/Supervisor.kt b/common/kotlinx-coroutines-core-common/src/Supervisor.kt new file mode 100644 index 0000000000..d8d3539cf4 --- /dev/null +++ b/common/kotlinx-coroutines-core-common/src/Supervisor.kt @@ -0,0 +1,41 @@ +/* + * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +package kotlinx.coroutines.experimental + +import kotlin.coroutines.experimental.* + +@Suppress("FunctionName") +public fun SupervisorJob(parent: Job? = null) : Job = SupervisorJobImpl(parent) + +public suspend fun supervisorScope(block: suspend CoroutineScope.() -> R): R { + // todo: optimize implementation to a single allocated object + // todo: fix copy-and-paste with coroutineScope + val owner = SupervisorCoroutine(coroutineContext) + owner.start(CoroutineStart.UNDISPATCHED, owner, block) + owner.join() + if (owner.isCancelled) { + throw owner.getCancellationException().let { it.cause ?: it } + } + val state = owner.state + if (state is CompletedExceptionally) { + throw state.cause + } + @Suppress("UNCHECKED_CAST") + return state as R + +} + +private class SupervisorJobImpl(parent: Job?) : JobSupport(true) { + init { initParentJobInternal(parent) } + override val onFailComplete get() = true + override val handlesException: Boolean get() = false + override fun childFailed(cause: Throwable): Boolean = false +} + +private class SupervisorCoroutine( + parentContext: CoroutineContext +) : AbstractCoroutine(parentContext, true) { + override fun childFailed(cause: Throwable): Boolean = false +} diff --git a/common/kotlinx-coroutines-core-common/test/SupervisorTest.kt b/common/kotlinx-coroutines-core-common/test/SupervisorTest.kt new file mode 100644 index 0000000000..8d8fdf5648 --- /dev/null +++ b/common/kotlinx-coroutines-core-common/test/SupervisorTest.kt @@ -0,0 +1,58 @@ +/* + * Copyright 2016-2018 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license. + */ + +@file:Suppress("NAMED_ARGUMENTS_NOT_ALLOWED") // KT-21913 + +package kotlinx.coroutines.experimental + +import kotlin.test.* + +class SupervisorTest : TestBase() { + @Test + fun testSupervisorJob() = runTest( + unhandled = listOf( + { it -> it is TestException2 }, + { it -> it is TestException1 } + ) + ) { + expect(1) + val supervisor = SupervisorJob() + val job1 = launch(supervisor + CoroutineName("job1")) { + expect(2) + yield() // to second child + expect(4) + throw TestException1() + } + val job2 = launch(supervisor + CoroutineName("job2")) { + expect(3) + throw TestException2() + } + joinAll(job1, job2) + finish(5) + assertTrue(job1.isFailed) + assertTrue(job2.isFailed) + } + + @Test + fun testSupervisorScope() = runTest( + unhandled = listOf( + { it -> it is TestException1 }, + { it -> it is TestException2 } + ) + ) { + val result = supervisorScope { + launch { + throw TestException1() + } + launch { + throw TestException2() + } + "OK" + } + assertEquals("OK", result) + } + + private class TestException1 : Exception() + private class TestException2 : Exception() +} \ No newline at end of file diff --git a/core/kotlinx-coroutines-core/test/TestBase.kt b/core/kotlinx-coroutines-core/test/TestBase.kt index e03242d768..21e4faa870 100644 --- a/core/kotlinx-coroutines-core/test/TestBase.kt +++ b/core/kotlinx-coroutines-core/test/TestBase.kt @@ -146,7 +146,6 @@ public actual open class TestBase actual constructor() { !unhandled[exCount - 1](e) -> printError("Unhandled exception was unexpected: $e", e) } - context[Job]?.cancel(e) }) } catch (e: Throwable) { ex = e diff --git a/js/kotlinx-coroutines-core-js/test/TestBase.kt b/js/kotlinx-coroutines-core-js/test/TestBase.kt index 473f279d57..7873036288 100644 --- a/js/kotlinx-coroutines-core-js/test/TestBase.kt +++ b/js/kotlinx-coroutines-core-js/test/TestBase.kt @@ -81,7 +81,6 @@ public actual open class TestBase actual constructor() { !unhandled[exCount - 1](e) -> printError("Unhandled exception was unexpected: $e", e) } - context[Job]?.cancel(e) }).catch { e -> ex = e if (expected != null) { diff --git a/native/kotlinx-coroutines-core-native/test/TestBase.kt b/native/kotlinx-coroutines-core-native/test/TestBase.kt index 7f75e4439e..81e680b5fa 100644 --- a/native/kotlinx-coroutines-core-native/test/TestBase.kt +++ b/native/kotlinx-coroutines-core-native/test/TestBase.kt @@ -76,7 +76,6 @@ public actual open class TestBase actual constructor() { !unhandled[exCount - 1](e) -> printError("Unhandled exception was unexpected: $e", e) } - context[Job]?.cancel(e) }) } catch (e: Throwable) { ex = e