Skip to content

Commit d0b0e5e

Browse files
committed
Improve documentation of CompletableJob.completeExceptionally, add tests to ensure its contract
Addresses #1527
1 parent bda9c79 commit d0b0e5e

File tree

2 files changed

+58
-2
lines changed

2 files changed

+58
-2
lines changed

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

+3
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ public interface CompletableJob : Job {
3131
* This function transitions this job into _cancelled_ state if it was not completed or cancelled yet.
3232
* However, that if this job has children, then it transitions into _cancelling_ state and becomes _cancelled_
3333
* once all its children are [complete][isCompleted]. See [Job] for details.
34+
*
35+
* Its responsibility of the caller to properly handle and report the given [exception], all job's children will receive
36+
* a [CancellationException] with the [exception] as a cause for the sake of diagnostic.
3437
*/
3538
public fun completeExceptionally(exception: Throwable): Boolean
3639
}

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

+55-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ package kotlinx.coroutines
66

77
import kotlin.test.*
88

9-
class CompletableJobTest {
9+
class CompletableJobTest : TestBase() {
1010
@Test
1111
fun testComplete() {
1212
val job = Job()
@@ -46,4 +46,57 @@ class CompletableJobTest {
4646
assertFalse(child.isActive)
4747
assertFalse(parent.isActive)
4848
}
49-
}
49+
50+
@Test
51+
fun testExceptionIsNotReportedToChildren() = parametrized { job ->
52+
expect(1)
53+
val child = launch(job) {
54+
expect(2)
55+
try {
56+
// KT-33840
57+
hang {}
58+
} catch (e: Throwable) {
59+
assertTrue(e is CancellationException)
60+
assertTrue((if (RECOVER_STACK_TRACES) e.cause?.cause else e.cause) is TestException)
61+
expect(4)
62+
throw e
63+
}
64+
}
65+
yield()
66+
expect(3)
67+
job.completeExceptionally(TestException())
68+
child.join()
69+
finish(5)
70+
}
71+
72+
@Test
73+
fun testCompleteExceptionallyDoesntAffectDeferred() = parametrized { job ->
74+
expect(1)
75+
val child = async(job) {
76+
expect(2)
77+
try {
78+
// KT-33840
79+
hang {}
80+
} catch (e: Throwable) {
81+
assertTrue(e is CancellationException)
82+
assertTrue((if (RECOVER_STACK_TRACES) e.cause?.cause else e.cause) is TestException)
83+
expect(4)
84+
throw e
85+
}
86+
}
87+
yield()
88+
expect(3)
89+
job.completeExceptionally(TestException())
90+
child.join()
91+
assertTrue { child.getCompletionExceptionOrNull() is CancellationException }
92+
finish(5)
93+
}
94+
95+
private fun parametrized(block: suspend CoroutineScope.(CompletableJob) -> Unit) {
96+
runTest {
97+
block(Job())
98+
reset()
99+
block(SupervisorJob())
100+
}
101+
}
102+
}

0 commit comments

Comments
 (0)