Skip to content

Commit 6aedb61

Browse files
committed
Make TimeoutCancellationException copyable
* Make CopyableThrowable common, so multiplatform exceptions could be recovered on JVM
1 parent 83943ef commit 6aedb61

File tree

7 files changed

+52
-29
lines changed

7 files changed

+52
-29
lines changed

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

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,33 @@ internal expect val DEBUG: Boolean
88
internal expect val Any.hexAddress: String
99
internal expect val Any.classSimpleName: String
1010
internal expect fun assert(value: () -> Boolean)
11+
12+
/**
13+
* Throwable which can be cloned during stacktrace recovery in a class-specific way.
14+
* For additional information about stacktrace recovery see [STACKTRACE_RECOVERY_PROPERTY_NAME]
15+
*
16+
* Example of usage:
17+
* ```
18+
* class BadResponseCodeException(val responseCode: Int) : Exception(), CopyableThrowable<BadResponseCodeException> {
19+
*
20+
* override fun createCopy(): BadResponseCodeException {
21+
* val result = BadResponseCodeException(responseCode)
22+
* result.initCause(this)
23+
* return result
24+
* }
25+
* ```
26+
*
27+
* Copy mechanism is used only on JVM, but it might be convenient to implement it in common exceptions,
28+
* so on JVM their stacktraces will be properly recovered.
29+
*/
30+
@ExperimentalCoroutinesApi
31+
public interface CopyableThrowable<T> where T : Throwable, T : CopyableThrowable<T> {
32+
33+
/**
34+
* Creates a copy of the current instance.
35+
* For better debuggability, it is recommended to use original exception as [cause][Throwable.cause] of the resulting one.
36+
* Stacktrace of copied exception will be overwritten by stacktrace recovery machinery by [Throwable.setStackTrace] call.
37+
* An exception can opt-out of copying by returning `null` from this function.
38+
*/
39+
public fun createCopy(): T?
40+
}

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

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ private fun <U, T: U> setupTimeout(
7878
return coroutine.startUndispatchedOrReturnIgnoreTimeout(coroutine, block)
7979
}
8080

81-
private open class TimeoutCoroutine<U, in T: U>(
81+
private class TimeoutCoroutine<U, in T: U>(
8282
@JvmField val time: Long,
8383
uCont: Continuation<U> // unintercepted continuation
8484
) : ScopeCoroutine<T>(uCont.context, uCont), Runnable {
@@ -96,13 +96,17 @@ private open class TimeoutCoroutine<U, in T: U>(
9696
public class TimeoutCancellationException internal constructor(
9797
message: String,
9898
@JvmField internal val coroutine: Job?
99-
) : CancellationException(message) {
99+
) : CancellationException(message), CopyableThrowable<TimeoutCancellationException> {
100100
/**
101101
* Creates a timeout exception with the given message.
102102
* This constructor is needed for exception stack-traces recovery.
103103
*/
104104
@Suppress("UNUSED")
105105
internal constructor(message: String) : this(message, null)
106+
107+
// message is never null in fact
108+
override fun createCopy(): TimeoutCancellationException? =
109+
TimeoutCancellationException(message ?: "", coroutine).also { it.initCause(this) }
106110
}
107111

108112
@Suppress("FunctionName")

kotlinx-coroutines-core/common/src/internal/StackTraceRecovery.common.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,11 @@ import kotlin.coroutines.*
1717
*/
1818
internal expect fun <E: Throwable> recoverStackTrace(exception: E, continuation: Continuation<*>): E
1919

20+
/**
21+
* initCause on JVM, nop on other platforms
22+
*/
23+
internal expect fun Throwable.initCause(cause: Throwable)
24+
2025
/**
2126
* Tries to recover stacktrace for given [exception]. Used in non-suspendable points of awaiting.
2227
* Stacktrace recovery tries to instantiate exception of given type with original exception as a cause.

kotlinx-coroutines-core/js/src/internal/StackTraceRecovery.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,6 @@ internal actual interface CoroutineStackFrame {
2020

2121
@Suppress("ACTUAL_WITHOUT_EXPECT")
2222
internal actual typealias StackTraceElement = Any
23+
24+
internal actual fun Throwable.initCause(cause: Throwable) {
25+
}

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

Lines changed: 0 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -48,33 +48,6 @@ public const val DEBUG_PROPERTY_NAME = "kotlinx.coroutines.debug"
4848
*/
4949
internal const val STACKTRACE_RECOVERY_PROPERTY_NAME = "kotlinx.coroutines.stacktrace.recovery"
5050

51-
/**
52-
* Throwable which can be cloned during stacktrace recovery in a class-specific way.
53-
* For additional information about stacktrace recovery see [STACKTRACE_RECOVERY_PROPERTY_NAME]
54-
*
55-
* Example of usage:
56-
* ```
57-
* class BadResponseCodeException(val responseCode: Int) : Exception(), CopyableThrowable<BadResponseCodeException> {
58-
*
59-
* override fun createCopy(): BadResponseCodeException {
60-
* val result = BadResponseCodeException(responseCode)
61-
* result.initCause(this)
62-
* return result
63-
* }
64-
* ```
65-
*/
66-
@ExperimentalCoroutinesApi
67-
public interface CopyableThrowable<T> where T : Throwable, T : CopyableThrowable<T> {
68-
69-
/**
70-
* Creates a copy of the current instance.
71-
* For better debuggability, it is recommended to use original exception as [cause][Throwable.cause] of the resulting one.
72-
* Stacktrace of copied exception will be overwritten by stacktrace recovery machinery by [Throwable.setStackTrace] call.
73-
* An exception can opt-out of copying by returning `null` from this function.
74-
*/
75-
public fun createCopy(): T?
76-
}
77-
7851
/**
7952
* Automatic debug configuration value for [DEBUG_PROPERTY_NAME].
8053
*/

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

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -204,3 +204,8 @@ internal actual typealias CoroutineStackFrame = kotlin.coroutines.jvm.internal.C
204204

205205
@Suppress("ACTUAL_WITHOUT_EXPECT")
206206
internal actual typealias StackTraceElement = java.lang.StackTraceElement
207+
208+
internal actual fun Throwable.initCause(cause: Throwable) {
209+
// Resolved to member, verified by test
210+
initCause(cause)
211+
}

kotlinx-coroutines-core/native/src/internal/StackTraceRecovery.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,6 @@ internal actual interface CoroutineStackFrame {
1919

2020
@Suppress("ACTUAL_WITHOUT_EXPECT")
2121
internal actual typealias StackTraceElement = Any
22+
23+
internal actual fun Throwable.initCause(cause: Throwable) {
24+
}

0 commit comments

Comments
 (0)