Skip to content

Display artificial stack frames as calls to functions instead of text #3073

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Oct 20, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified docs/images/after.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/images/before.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
65 changes: 65 additions & 0 deletions kotlinx-coroutines-core/jvm/src/debug/CoroutineDebugging.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright 2016-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license.
*/

/* This package name is like this so that
1) the artificial stack frames look pretty, and
2) the IDE reliably navigates to this file. */
package _COROUTINE

/**
* A collection of artificial stack trace elements to be included in stack traces by the coroutines machinery.
*
* There are typically two ways in which one can encounter an artificial stack frame:
* 1. By using the debug mode, via the stacktrace recovery mechanism; see
* [stacktrace recovery](https://github.com/Kotlin/kotlinx.coroutines/blob/master/docs/topics/debugging.md#stacktrace-recovery)
* documentation. The usual way to enable the debug mode is with the [kotlinx.coroutines.DEBUG_PROPERTY_NAME] system
* property.
* 2. By looking at the output of DebugProbes; see the
* [kotlinx-coroutines-debug](https://github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-debug) module.
*/
internal class ArtificialStackFrames {
/**
* Returns an artificial stack trace element denoting the boundary between coroutine creation and its execution.
*
* Appearance of this function in stack traces does not mean that it was called. Instead, it is used as a marker
* that separates the part of the stack trace with the code executed in a coroutine from the stack trace of the code
* that launched the coroutine.
*
* In earlier versions of kotlinx-coroutines, this was displayed as "(Coroutine creation stacktrace)", which caused
* problems for tooling that processes stack traces: https://github.com/Kotlin/kotlinx.coroutines/issues/2291
*
* Note that presence of this marker in a stack trace implies that coroutine creation stack traces were enabled.
*/
fun coroutineCreation(): StackTraceElement = Exception().artificialFrame(_CREATION::class.java.simpleName)

/**
* Returns an artificial stack trace element denoting a coroutine boundary.
*
* Appearance of this function in stack traces does not mean that it was called. Instead, when one coroutine invokes
* another, this is used as a marker in the stack trace to denote where the execution of one coroutine ends and that
* of another begins.
*
* In earlier versions of kotlinx-coroutines, this was displayed as "(Coroutine boundary)", which caused
* problems for tooling that processes stack traces: https://github.com/Kotlin/kotlinx.coroutines/issues/2291
*/
fun coroutineBoundary(): StackTraceElement = Exception().artificialFrame(_BOUNDARY::class.java.simpleName)
}

// These are needed for the IDE navigation to detect that this file does contain the definition.
private class _CREATION
private class _BOUNDARY

internal val ARTIFICIAL_FRAME_PACKAGE_NAME = "_COROUTINE"

/**
* Forms an artificial stack frame with the given class name.
*
* It consists of the following parts:
* 1. The package name, it seems, is needed for the IDE to detect stack trace elements reliably. It is `_COROUTINE` since
* this is a valid identifier.
* 2. Class names represents what type of artificial frame this is.
* 3. The method name is `_`. The methods not being present in class definitions does not seem to affect navigation.
*/
private fun Throwable.artificialFrame(name: String): StackTraceElement =
with(stackTrace[0]) { StackTraceElement(ARTIFICIAL_FRAME_PACKAGE_NAME + "." + name, "_", fileName, lineNumber) }
34 changes: 16 additions & 18 deletions kotlinx-coroutines-core/jvm/src/debug/internal/DebugProbesImpl.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ package kotlinx.coroutines.debug.internal

import kotlinx.atomicfu.*
import kotlinx.coroutines.*
import kotlinx.coroutines.internal.*
import kotlinx.coroutines.internal.ScopeCoroutine
import java.io.*
import java.lang.StackTraceElement
Expand All @@ -17,10 +16,10 @@ import kotlin.concurrent.*
import kotlin.coroutines.*
import kotlin.coroutines.jvm.internal.CoroutineStackFrame
import kotlin.synchronized
import kotlinx.coroutines.internal.artificialFrame as createArtificialFrame // IDEA bug workaround
import _COROUTINE.ArtificialStackFrames

internal object DebugProbesImpl {
private const val ARTIFICIAL_FRAME_MESSAGE = "Coroutine creation stacktrace"
private val ARTIFICIAL_FRAME = ArtificialStackFrames().coroutineCreation()
private val dateFormat = SimpleDateFormat("yyyy/MM/dd HH:mm:ss")

private var weakRefCleanerThread: Thread? = null
Expand Down Expand Up @@ -298,7 +297,7 @@ internal object DebugProbesImpl {
info.state
out.print("\n\nCoroutine ${owner.delegate}, state: $state")
if (observedStackTrace.isEmpty()) {
out.print("\n\tat ${createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE)}")
out.print("\n\tat $ARTIFICIAL_FRAME")
printStackTrace(out, info.creationStackTrace)
} else {
printStackTrace(out, enhancedStackTrace)
Expand Down Expand Up @@ -500,15 +499,17 @@ internal object DebugProbesImpl {
return createOwner(completion, frame)
}

private fun List<StackTraceElement>.toStackTraceFrame(): StackTraceFrame? =
foldRight<StackTraceElement, StackTraceFrame?>(null) { frame, acc ->
StackTraceFrame(acc, frame)
}
private fun List<StackTraceElement>.toStackTraceFrame(): StackTraceFrame =
StackTraceFrame(
foldRight<StackTraceElement, StackTraceFrame?>(null) { frame, acc ->
StackTraceFrame(acc, frame)
}, ARTIFICIAL_FRAME
)

private fun <T> createOwner(completion: Continuation<T>, frame: StackTraceFrame?): Continuation<T> {
if (!isInstalled) return completion
val info = DebugCoroutineInfoImpl(completion.context, frame, sequenceNumber.incrementAndGet())
val owner = CoroutineOwner(completion, info, frame)
val owner = CoroutineOwner(completion, info)
capturedCoroutinesMap[owner] = true
if (!isInstalled) capturedCoroutinesMap.clear()
return owner
Expand All @@ -531,9 +532,9 @@ internal object DebugProbesImpl {
*/
private class CoroutineOwner<T>(
@JvmField val delegate: Continuation<T>,
@JvmField val info: DebugCoroutineInfoImpl,
private val frame: CoroutineStackFrame?
@JvmField val info: DebugCoroutineInfoImpl
) : Continuation<T> by delegate, CoroutineStackFrame {
private val frame get() = info.creationStackBottom

override val callerFrame: CoroutineStackFrame?
get() = frame?.callerFrame
Expand All @@ -551,12 +552,10 @@ internal object DebugProbesImpl {
private fun <T : Throwable> sanitizeStackTrace(throwable: T): List<StackTraceElement> {
val stackTrace = throwable.stackTrace
val size = stackTrace.size
val probeIndex = stackTrace.indexOfLast { it.className == "kotlin.coroutines.jvm.internal.DebugProbesKt" }
val traceStart = 1 + stackTrace.indexOfLast { it.className == "kotlin.coroutines.jvm.internal.DebugProbesKt" }

if (!sanitizeStackTraces) {
return List(size - probeIndex) {
if (it == 0) createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE) else stackTrace[it + probeIndex]
}
return List(size - traceStart) { stackTrace[it + traceStart] }
}

/*
Expand All @@ -567,9 +566,8 @@ internal object DebugProbesImpl {
* If an interval of internal methods ends in a synthetic method, the outermost non-synthetic method in that
* interval will also be included.
*/
val result = ArrayList<StackTraceElement>(size - probeIndex + 1)
result += createArtificialFrame(ARTIFICIAL_FRAME_MESSAGE)
var i = probeIndex + 1
val result = ArrayList<StackTraceElement>(size - traceStart + 1)
var i = traceStart
while (i < size) {
if (stackTrace[i].isInternalMethod) {
result += stackTrace[i] // we include the boundary of the span in any case
Expand Down
17 changes: 8 additions & 9 deletions kotlinx-coroutines-core/jvm/src/internal/StackTraceRecovery.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
package kotlinx.coroutines.internal

import kotlinx.coroutines.*
import _COROUTINE.ARTIFICIAL_FRAME_PACKAGE_NAME
import _COROUTINE.ArtificialStackFrames
import java.util.*
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.*
Expand All @@ -18,6 +20,8 @@ import kotlin.coroutines.intrinsics.*
private const val baseContinuationImplClass = "kotlin.coroutines.jvm.internal.BaseContinuationImpl"
private const val stackTraceRecoveryClass = "kotlinx.coroutines.internal.StackTraceRecoveryKt"

private val ARTIFICIAL_FRAME = ArtificialStackFrames().coroutineBoundary()

private val baseContinuationImplClassName = runCatching {
Class.forName(baseContinuationImplClass).canonicalName
}.getOrElse { baseContinuationImplClass }
Expand All @@ -42,7 +46,7 @@ private fun <E : Throwable> E.sanitizeStackTrace(): E {
val adjustment = if (endIndex == -1) 0 else size - endIndex
val trace = Array(size - lastIntrinsic - adjustment) {
if (it == 0) {
artificialFrame("Coroutine boundary")
ARTIFICIAL_FRAME
} else {
stackTrace[startIndex + it - 1]
}
Expand Down Expand Up @@ -97,13 +101,13 @@ private fun <E : Throwable> tryCopyAndVerify(exception: E): E? {
* IllegalStateException
* at foo
* at kotlin.coroutines.resumeWith
* (Coroutine boundary)
* at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)
* at bar
* ...real stackTrace...
* caused by "IllegalStateException" (original one)
*/
private fun <E : Throwable> createFinalException(cause: E, result: E, resultStackTrace: ArrayDeque<StackTraceElement>): E {
resultStackTrace.addFirst(artificialFrame("Coroutine boundary"))
resultStackTrace.addFirst(ARTIFICIAL_FRAME)
val causeTrace = cause.stackTrace
val size = causeTrace.frameIndex(baseContinuationImplClassName)
if (size == -1) {
Expand Down Expand Up @@ -193,12 +197,7 @@ private fun createStackTrace(continuation: CoroutineStackFrame): ArrayDeque<Stac
return stack
}

/**
* @suppress
*/
@InternalCoroutinesApi
public fun artificialFrame(message: String): StackTraceElement = java.lang.StackTraceElement("\b\b\b($message", "\b", "\b", -1)
internal fun StackTraceElement.isArtificial() = className.startsWith("\b\b\b")
internal fun StackTraceElement.isArtificial() = className.startsWith(ARTIFICIAL_FRAME_PACKAGE_NAME)
private fun Array<StackTraceElement>.frameIndex(methodName: String) = indexOfFirst { methodName == it.className }

private fun StackTraceElement.elementWiseEquals(e: StackTraceElement): Boolean {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferFromScope$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:109)
(Coroutine boundary)
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.sendInChannel(StackTraceRecoveryChannelsTest.kt:167)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$sendWithContext$2.invokeSuspend(StackTraceRecoveryChannelsTest.kt:162)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$sendFromScope$2.invokeSuspend(StackTraceRecoveryChannelsTest.kt:172)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferFromScope$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:112)
Caused by: kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferFromScope$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:109)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferWithContextWrapped$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:98)
(Coroutine boundary)
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.sendInChannel(StackTraceRecoveryChannelsTest.kt:199)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$sendWithContext$2.invokeSuspend(StackTraceRecoveryChannelsTest.kt:194)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferWithContextWrapped$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:100)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferWithCurrentContext$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:86)
(Coroutine boundary)
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.sendInChannel(StackTraceRecoveryChannelsTest.kt:210)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$sendWithContext$2.invokeSuspend(StackTraceRecoveryChannelsTest.kt:205)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testOfferWithCurrentContext$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:89)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:97)
(Coroutine boundary)
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelReceive(StackTraceRecoveryChannelsTest.kt:116)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:101)
Caused by: kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:97)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:110)
(Coroutine boundary)
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelReceive(StackTraceRecoveryChannelsTest.kt:116)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:111)
Caused by: kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testReceiveFromClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:110)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
kotlinx.coroutines.RecoverableTestCancellationException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendFromScope$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:136)
(Coroutine boundary)
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.sendInChannel(StackTraceRecoveryChannelsTest.kt:167)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$sendWithContext$2.invokeSuspend(StackTraceRecoveryChannelsTest.kt:162)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$sendFromScope$2.invokeSuspend(StackTraceRecoveryChannelsTest.kt:172)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendFromScope$1$deferred$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:126)
Caused by: kotlinx.coroutines.RecoverableTestCancellationException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendFromScope$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:136)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ java.util.concurrent.CancellationException: RendezvousChannel was cancelled
at kotlinx.coroutines.channels.AbstractChannel.cancel(AbstractChannel.kt:630)
at kotlinx.coroutines.channels.ReceiveChannel$DefaultImpls.cancel$default(Channel.kt:311)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:52)
(Coroutine boundary)
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelSend(StackTraceRecoveryChannelsTest.kt:73)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:56)
Caused by: java.util.concurrent.CancellationException: RendezvousChannel was cancelled
at kotlinx.coroutines.channels.AbstractChannel.cancel(AbstractChannel.kt:630)
at kotlinx.coroutines.channels.ReceiveChannel$DefaultImpls.cancel$default(Channel.kt:311)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToChannel$1$job$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:52)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:43)
(Coroutine boundary)
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest.channelSend(StackTraceRecoveryChannelsTest.kt:74)
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:44)
Caused by: kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryChannelsTest$testSendToClosedChannel$1.invokeSuspend(StackTraceRecoveryChannelsTest.kt:43)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:61)
at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testEventLoopDispatcher$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:40)
(Coroutine boundary)
at _COROUTINE._BOUNDARY._(CoroutineDebugging.kt)
at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$withContext$2.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:76)
at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.doFastPath(StackTraceRecoveryResumeModeTest.kt:71)
at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:62)
at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testEventLoopDispatcher$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:40)
Caused by: kotlinx.coroutines.RecoverableTestException
at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest.testResumeModeFastPath(StackTraceRecoveryResumeModeTest.kt:61)
at kotlinx.coroutines.exceptions.StackTraceRecoveryResumeModeTest$testEventLoopDispatcher$1.invokeSuspend(StackTraceRecoveryResumeModeTest.kt:40)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
Loading