Skip to content

Commit 29493e1

Browse files
authored
Merge pull request #792 from Kotlin/stacktrace-recovery
Stacktrace recovery Fixes #493 Fixes #74
2 parents b733f7d + cd162d3 commit 29493e1

File tree

54 files changed

+2721
-139
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

54 files changed

+2721
-139
lines changed

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ GlobalScope.launch {
3232
* [core](core/README.md) — Kotlin/JVM implementation of common coroutines with additional features:
3333
* `Dispatchers.IO` dispatcher for blocking coroutines;
3434
* `Executor.asCoroutineDispatcher()` extension, custom thread pools, and more.
35+
* [debug](core/README.md) — debug utilities for coroutines.
36+
* `DebugProbes` API to probe, keep track of, print and dump active coroutines.
3537
* [js](js/README.md) — Kotlin/JS implementation of common coroutines with `Promise` support.
3638
* [native](native/README.md) — Kotlin/Native implementation of common coroutines with `runBlocking` single-threaded event loop.
3739
* [reactive](reactive/README.md) — modules that provide builders and iteration support for various reactive streams libraries:

RELEASE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ To release new `<version>` of `kotlinx-coroutines`:
1212
`git merge origin/master`
1313

1414
4. Search & replace `<old-version>` with `<version>` across the project files. Should replace in:
15-
* [`README.md`](README.md)
15+
* [`README.md`](README.md) (native, core, test, debug, modules)
1616
* [`coroutines-guide.md`](docs/coroutines-guide.md)
1717
* [`gradle.properties`](gradle.properties)
1818
* [`ui/kotlinx-coroutines-android/example-app/gradle.properties`](ui/kotlinx-coroutines-android/example-app/gradle.properties)

binary-compatibility-validator/build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ dependencies {
1313
testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"
1414

1515
testArtifacts project(':kotlinx-coroutines-core')
16+
testArtifacts project(':kotlinx-coroutines-debug')
1617

1718
testArtifacts project(':kotlinx-coroutines-reactive')
1819
testArtifacts project(':kotlinx-coroutines-reactor')

binary-compatibility-validator/reference-public-api/kotlinx-coroutines-core.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,12 @@ public final class kotlinx/coroutines/CancellableContinuation$DefaultImpls {
5050
public static synthetic fun tryResume$default (Lkotlinx/coroutines/CancellableContinuation;Ljava/lang/Object;Ljava/lang/Object;ILjava/lang/Object;)Ljava/lang/Object;
5151
}
5252

53-
public class kotlinx/coroutines/CancellableContinuationImpl : java/lang/Runnable, kotlinx/coroutines/CancellableContinuation {
53+
public class kotlinx/coroutines/CancellableContinuationImpl : java/lang/Runnable, kotlin/coroutines/jvm/internal/CoroutineStackFrame, kotlinx/coroutines/CancellableContinuation {
5454
public fun <init> (Lkotlin/coroutines/Continuation;I)V
5555
public fun completeResume (Ljava/lang/Object;)V
56+
public fun getCallerFrame ()Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;
5657
public fun getContext ()Lkotlin/coroutines/CoroutineContext;
58+
public fun getStackTraceElement ()Ljava/lang/StackTraceElement;
5759
public fun getSuccessfulResult (Ljava/lang/Object;)Ljava/lang/Object;
5860
public fun initCancellability ()V
5961
protected fun nameString ()Ljava/lang/String;
@@ -349,6 +351,7 @@ public class kotlinx/coroutines/JobSupport : kotlinx/coroutines/ChildJob, kotlin
349351
public fun plus (Lkotlinx/coroutines/Job;)Lkotlinx/coroutines/Job;
350352
public final fun registerSelectClause0 (Lkotlinx/coroutines/selects/SelectInstance;Lkotlin/jvm/functions/Function1;)V
351353
public final fun start ()Z
354+
public final fun toDebugString ()Ljava/lang/String;
352355
public fun toString ()Ljava/lang/String;
353356
}
354357

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
public final class kotlinx/coroutines/debug/CoroutineState {
2+
public final fun component1 ()Lkotlin/coroutines/Continuation;
3+
public final fun copy (Lkotlin/coroutines/Continuation;Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;J)Lkotlinx/coroutines/debug/CoroutineState;
4+
public static synthetic fun copy$default (Lkotlinx/coroutines/debug/CoroutineState;Lkotlin/coroutines/Continuation;Lkotlin/coroutines/jvm/internal/CoroutineStackFrame;JILjava/lang/Object;)Lkotlinx/coroutines/debug/CoroutineState;
5+
public fun equals (Ljava/lang/Object;)Z
6+
public final fun getContinuation ()Lkotlin/coroutines/Continuation;
7+
public final fun getCreationStackTrace ()Ljava/util/List;
8+
public final fun getJob ()Lkotlinx/coroutines/Job;
9+
public final fun getJobOrNull ()Lkotlinx/coroutines/Job;
10+
public final fun getState ()Lkotlinx/coroutines/debug/State;
11+
public fun hashCode ()I
12+
public final fun lastObservedStackTrace ()Ljava/util/List;
13+
public fun toString ()Ljava/lang/String;
14+
}
15+
16+
public final class kotlinx/coroutines/debug/DebugProbes {
17+
public static final field INSTANCE Lkotlinx/coroutines/debug/DebugProbes;
18+
public final fun dumpCoroutines (Ljava/io/PrintStream;)V
19+
public static synthetic fun dumpCoroutines$default (Lkotlinx/coroutines/debug/DebugProbes;Ljava/io/PrintStream;ILjava/lang/Object;)V
20+
public final fun dumpCoroutinesState ()Ljava/util/List;
21+
public final fun getSanitizeStackTraces ()Z
22+
public final fun hierarchyToString (Lkotlinx/coroutines/Job;)Ljava/lang/String;
23+
public final fun install ()V
24+
public final fun printHierarchy (Lkotlinx/coroutines/Job;Ljava/io/PrintStream;)V
25+
public static synthetic fun printHierarchy$default (Lkotlinx/coroutines/debug/DebugProbes;Lkotlinx/coroutines/Job;Ljava/io/PrintStream;ILjava/lang/Object;)V
26+
public final fun setSanitizeStackTraces (Z)V
27+
public final fun uninstall ()V
28+
public final fun withDebugProbes (Lkotlin/jvm/functions/Function0;)V
29+
}
30+
31+
public final class kotlinx/coroutines/debug/State : java/lang/Enum {
32+
public static final field CREATED Lkotlinx/coroutines/debug/State;
33+
public static final field RUNNING Lkotlinx/coroutines/debug/State;
34+
public static final field SUSPENDED Lkotlinx/coroutines/debug/State;
35+
public static fun valueOf (Ljava/lang/String;)Lkotlinx/coroutines/debug/State;
36+
public static fun values ()[Lkotlinx/coroutines/debug/State;
37+
}
38+

build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ buildscript {
4040
classpath "com.moowork.gradle:gradle-node-plugin:$gradle_node_version"
4141

4242
// JMH plugins
43-
classpath "com.github.jengelman.gradle.plugins:shadow:2.0.2"
43+
classpath "com.github.jengelman.gradle.plugins:shadow:4.0.2"
4444
classpath "me.champeau.gradle:jmh-gradle-plugin:0.4.7"
4545
classpath "net.ltgt.gradle:gradle-apt-plugin:0.10"
4646
}

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ internal abstract class AbstractContinuation<in T>(
134134
if (trySuspend()) return COROUTINE_SUSPENDED
135135
// otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state
136136
val state = this.state
137-
if (state is CompletedExceptionally) throw state.cause
137+
if (state is CompletedExceptionally) throw recoverStackTrace(state.cause, this)
138138
return getSuccessfulResult(state)
139139
}
140140

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

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -218,10 +218,15 @@ private class DisposeOnCancel(private val handle: DisposableHandle) : CancelHand
218218
internal open class CancellableContinuationImpl<in T>(
219219
delegate: Continuation<T>,
220220
resumeMode: Int
221-
) : AbstractContinuation<T>(delegate, resumeMode), CancellableContinuation<T>, Runnable {
221+
) : AbstractContinuation<T>(delegate, resumeMode), CancellableContinuation<T>, Runnable, CoroutineStackFrame {
222222

223223
public override val context: CoroutineContext = delegate.context
224224

225+
override val callerFrame: CoroutineStackFrame?
226+
get() = delegate as? CoroutineStackFrame
227+
228+
override fun getStackTraceElement(): StackTraceElement? = null
229+
225230
override fun initCancellability() {
226231
initParentJobInternal(delegate.context[Job])
227232
}

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

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -82,10 +82,12 @@ internal object UndispatchedEventLoop {
8282
internal class DispatchedContinuation<in T>(
8383
@JvmField val dispatcher: CoroutineDispatcher,
8484
@JvmField val continuation: Continuation<T>
85-
) : DispatchedTask<T>(MODE_ATOMIC_DEFAULT), Continuation<T> by continuation {
85+
) : DispatchedTask<T>(MODE_ATOMIC_DEFAULT), CoroutineStackFrame, Continuation<T> by continuation {
8686
@JvmField
8787
@Suppress("PropertyName")
8888
internal var _state: Any? = UNDEFINED
89+
override val callerFrame: CoroutineStackFrame? = continuation as? CoroutineStackFrame
90+
override fun getStackTraceElement(): StackTraceElement? = null
8991
@JvmField // pre-cached value to avoid ctx.fold on every resumption
9092
internal val countOrElement = threadContextElements(context)
9193

@@ -168,7 +170,7 @@ internal class DispatchedContinuation<in T>(
168170
@Suppress("NOTHING_TO_INLINE") // we need it inline to save us an entry on the stack
169171
inline fun resumeUndispatchedWithException(exception: Throwable) {
170172
withCoroutineContext(context, countOrElement) {
171-
continuation.resumeWithException(exception)
173+
continuation.resumeWithStackTrace(exception)
172174
}
173175
}
174176

@@ -191,7 +193,7 @@ internal fun <T> Continuation<T>.resumeCancellable(value: T) = when (this) {
191193

192194
internal fun <T> Continuation<T>.resumeCancellableWithException(exception: Throwable) = when (this) {
193195
is DispatchedContinuation -> resumeCancellableWithException(exception)
194-
else -> resumeWithException(exception)
196+
else -> resumeWithStackTrace(exception)
195197
}
196198

197199
internal fun <T> Continuation<T>.resumeDirect(value: T) = when (this) {
@@ -200,8 +202,8 @@ internal fun <T> Continuation<T>.resumeDirect(value: T) = when (this) {
200202
}
201203

202204
internal fun <T> Continuation<T>.resumeDirectWithException(exception: Throwable) = when (this) {
203-
is DispatchedContinuation -> continuation.resumeWithException(exception)
204-
else -> resumeWithException(exception)
205+
is DispatchedContinuation -> continuation.resumeWithStackTrace(exception)
206+
else -> resumeWithStackTrace(exception)
205207
}
206208

207209
internal abstract class DispatchedTask<in T>(
@@ -232,7 +234,7 @@ internal abstract class DispatchedTask<in T>(
232234
else {
233235
val exception = getExceptionalResult(state)
234236
if (exception != null)
235-
continuation.resumeWithException(exception)
237+
continuation.resumeWithStackTrace(exception)
236238
else
237239
continuation.resume(getSuccessfulResult(state))
238240
}
@@ -276,3 +278,9 @@ internal fun <T> DispatchedTask<T>.resume(delegate: Continuation<T>, useMode: In
276278
delegate.resumeMode(getSuccessfulResult(state), useMode)
277279
}
278280
}
281+
282+
283+
@Suppress("NOTHING_TO_INLINE")
284+
internal inline fun Continuation<*>.resumeWithStackTrace(exception: Throwable) {
285+
resumeWith(Result.failure(recoverStackTrace(exception, this)))
286+
}

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -247,8 +247,9 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
247247
val seenExceptions = identitySet<Throwable>(exceptions.size)
248248
var suppressed = false
249249
for (exception in exceptions) {
250-
if (exception !== rootCause && exception !is CancellationException && seenExceptions.add(exception)) {
251-
rootCause.addSuppressedThrowable(exception)
250+
val unwrapped = unwrap(exception)
251+
if (unwrapped !== rootCause && unwrapped !is CancellationException && seenExceptions.add(unwrapped)) {
252+
rootCause.addSuppressedThrowable(unwrapped)
252253
suppressed = true
253254
}
254255
}
@@ -929,7 +930,10 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
929930

930931
// for nicer debugging
931932
public override fun toString(): String =
932-
"${nameString()}{${stateString(state)}}@$hexAddress"
933+
"${toDebugString()}@$hexAddress"
934+
935+
@InternalCoroutinesApi
936+
public fun toDebugString(): String = "${nameString()}{${stateString(state)}}"
933937

934938
/**
935939
* @suppress **This is unstable API and it is subject to change.**
@@ -1083,7 +1087,9 @@ public open class JobSupport constructor(active: Boolean) : Job, ChildJob, Paren
10831087
val state = this.state
10841088
if (state !is Incomplete) {
10851089
// already complete -- just return result
1086-
if (state is CompletedExceptionally) throw state.cause
1090+
if (state is CompletedExceptionally) { // Slow path to recover stacktrace
1091+
recoverAndThrow(state.cause)
1092+
}
10871093
return state.unboxState()
10881094

10891095
}

0 commit comments

Comments
 (0)