diff --git a/kotlinx-coroutines-core/common/src/CompletableJob.kt b/kotlinx-coroutines-core/common/src/CompletableJob.kt index f818749420..9a632276fe 100644 --- a/kotlinx-coroutines-core/common/src/CompletableJob.kt +++ b/kotlinx-coroutines-core/common/src/CompletableJob.kt @@ -31,6 +31,9 @@ public interface CompletableJob : Job { * This function transitions this job into _cancelled_ state if it was not completed or cancelled yet. * However, that if this job has children, then it transitions into _cancelling_ state and becomes _cancelled_ * once all its children are [complete][isCompleted]. See [Job] for details. + * + * Its responsibility of the caller to properly handle and report the given [exception], all job's children will receive + * a [CancellationException] with the [exception] as a cause for the sake of diagnostic. */ public fun completeExceptionally(exception: Throwable): Boolean } \ No newline at end of file diff --git a/kotlinx-coroutines-core/common/test/CompletableJobTest.kt b/kotlinx-coroutines-core/common/test/CompletableJobTest.kt index 335e5d5672..8b17e23556 100644 --- a/kotlinx-coroutines-core/common/test/CompletableJobTest.kt +++ b/kotlinx-coroutines-core/common/test/CompletableJobTest.kt @@ -6,7 +6,7 @@ package kotlinx.coroutines import kotlin.test.* -class CompletableJobTest { +class CompletableJobTest : TestBase() { @Test fun testComplete() { val job = Job() @@ -46,4 +46,57 @@ class CompletableJobTest { assertFalse(child.isActive) assertFalse(parent.isActive) } -} \ No newline at end of file + + @Test + fun testExceptionIsNotReportedToChildren() = parametrized { job -> + expect(1) + val child = launch(job) { + expect(2) + try { + // KT-33840 + hang {} + } catch (e: Throwable) { + assertTrue(e is CancellationException) + assertTrue((if (RECOVER_STACK_TRACES) e.cause?.cause else e.cause) is TestException) + expect(4) + throw e + } + } + yield() + expect(3) + job.completeExceptionally(TestException()) + child.join() + finish(5) + } + + @Test + fun testCompleteExceptionallyDoesntAffectDeferred() = parametrized { job -> + expect(1) + val child = async(job) { + expect(2) + try { + // KT-33840 + hang {} + } catch (e: Throwable) { + assertTrue(e is CancellationException) + assertTrue((if (RECOVER_STACK_TRACES) e.cause?.cause else e.cause) is TestException) + expect(4) + throw e + } + } + yield() + expect(3) + job.completeExceptionally(TestException()) + child.join() + assertTrue { child.getCompletionExceptionOrNull() is CancellationException } + finish(5) + } + + private fun parametrized(block: suspend CoroutineScope.(CompletableJob) -> Unit) { + runTest { + block(Job()) + reset() + block(SupervisorJob()) + } + } +}