Skip to content

Commit 2898f01

Browse files
committed
Make JobCancellationException copyable in debug mode
This simplifies trouble-shooting of "what caused cancellation", as you can print out exception and see its recovered stack trace.
1 parent 0aad8f1 commit 2898f01

File tree

6 files changed

+25
-8
lines changed

6 files changed

+25
-8
lines changed

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

+2-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,8 @@ class CoroutineScopeTest : TestBase() {
122122
expectUnreached()
123123
} catch (e: CancellationException) {
124124
expect(5)
125-
assertNull(e.cause)
125+
val cause = e.cause as JobCancellationException // shall be recovered JCE
126+
assertNull(cause.cause)
126127
}
127128
}
128129
repeat(3) { yield() } // let everything to start properly

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

+5-3
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,9 @@ class NonCancellableTest : TestBase() {
2929
try {
3030
job.await()
3131
expectUnreached()
32-
} catch (e: CancellationException) {
33-
assertNull(e.cause)
32+
} catch (e: JobCancellationException) {
33+
val cause = e.cause as JobCancellationException // shall be recovered JCE
34+
assertNull(cause.cause)
3435
finish(6)
3536
}
3637
}
@@ -120,7 +121,8 @@ class NonCancellableTest : TestBase() {
120121
job.await()
121122
expectUnreached()
122123
} catch (e: CancellationException) {
123-
assertNull(e.cause)
124+
val cause = e.cause as JobCancellationException // shall be recovered JCE
125+
assertNull(cause.cause)
124126
finish(7)
125127
}
126128
}

kotlinx-coroutines-core/jvm/src/Debug.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,9 @@ public interface CopyableThrowable<T> where T : Throwable, T : CopyableThrowable
4848
* Creates a copy of the current instance.
4949
* For better debuggability, it is recommended to use original exception as [cause][Throwable.cause] of the resulting one.
5050
* Stacktrace of copied exception will be overwritten by stacktrace recovery machinery by [Throwable.setStackTrace] call.
51+
* An exception can opt-out of copying by returning `null` from this function.
5152
*/
52-
public fun createCopy(): T
53+
public fun createCopy(): T?
5354
}
5455

5556
/**

kotlinx-coroutines-core/jvm/src/Exceptions.kt

+12-1
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ internal actual class JobCancellationException public actual constructor(
4141
message: String,
4242
cause: Throwable?,
4343
@JvmField internal actual val job: Job
44-
) : CancellationException(message) {
44+
) : CancellationException(message), CopyableThrowable<JobCancellationException> {
4545

4646
init {
4747
if (cause != null) initCause(cause)
@@ -60,6 +60,17 @@ internal actual class JobCancellationException public actual constructor(
6060
return this
6161
}
6262

63+
override fun createCopy(): JobCancellationException? {
64+
if (DEBUG) {
65+
return JobCancellationException(message!!, this, job)
66+
}
67+
68+
/*
69+
* In non-debug mode we don't copy JCE for speed as it does not have the stack trace anyway.
70+
*/
71+
return null
72+
}
73+
6374
override fun toString(): String = "${super.toString()}; job=$job"
6475

6576
@Suppress("DEPRECATION")

kotlinx-coroutines-core/jvm/src/internal/ExceptionsConstuctor.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ private val exceptionCtors: WeakHashMap<Class<out Throwable>, Ctor> = WeakHashMa
2020
internal fun <E : Throwable> tryCopyException(exception: E): E? {
2121
// Fast path for CopyableThrowable
2222
if (exception is CopyableThrowable<*>) {
23-
return runCatching { exception.createCopy() as E }.getOrNull()
23+
return runCatching { exception.createCopy() as E? }.getOrNull()
2424
}
2525
// Use cached ctor if found
2626
cacheLock.read { exceptionCtors[exception.javaClass] }?.let { cachedCtor ->

kotlinx-coroutines-core/jvm/test/exceptions/WithContextExceptionHandlingTest.kt

+3-1
Original file line numberDiff line numberDiff line change
@@ -171,8 +171,10 @@ class WithContextExceptionHandlingTest(private val mode: Mode) : TestBase() {
171171
@Test
172172
fun testCancel() = runTest {
173173
runOnlyCancellation(null) { e ->
174-
assertNull(e.cause)
174+
val cause = e.cause as JobCancellationException // shall be recovered JCE
175+
assertNull(cause.cause)
175176
assertTrue(e.suppressed.isEmpty())
177+
assertTrue(cause.suppressed.isEmpty())
176178
}
177179
}
178180

0 commit comments

Comments
 (0)