diff --git a/java/debugger/impl/resources/META-INF/java-debugger.xml b/java/debugger/impl/resources/META-INF/java-debugger.xml
index 5a46251d3f6b..c40e2c4cf693 100644
--- a/java/debugger/impl/resources/META-INF/java-debugger.xml
+++ b/java/debugger/impl/resources/META-INF/java-debugger.xml
@@ -205,6 +205,8 @@
description="Allow to compile the code before evaluation if needed"/>
+
diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/AsyncStacksUtils.java b/java/debugger/impl/src/com/intellij/debugger/engine/AsyncStacksUtils.java
index 665620482aa9..4a227b92dde8 100644
--- a/java/debugger/impl/src/com/intellij/debugger/engine/AsyncStacksUtils.java
+++ b/java/debugger/impl/src/com/intellij/debugger/engine/AsyncStacksUtils.java
@@ -141,25 +141,30 @@ public final class AsyncStacksUtils {
result -> result instanceof StringReference ? ((StringReference)result).value() : null,
evaluationContext);
if (value != null) {
- List res = new ArrayList<>();
- try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(value.getBytes(StandardCharsets.ISO_8859_1)))) {
- while (dis.available() > 0) {
- StackFrameItem item = null;
- if (dis.readBoolean()) {
- String className = dis.readUTF();
- String methodName = dis.readUTF();
- int line = dis.readInt();
- Location location =
- DebuggerUtilsEx.findOrCreateLocation(virtualMachineProxy.getVirtualMachine(), className, methodName, line);
- item = new StackFrameItem(location, null);
- }
- res.add(item);
+ return parseAgentAsyncStackTrace(value, virtualMachineProxy);
+ }
+ return null;
+ }
+
+ @ApiStatus.Internal
+ public static List parseAgentAsyncStackTrace(String value, VirtualMachineProxyImpl vm) {
+ List res = new ArrayList<>();
+ try (DataInputStream dis = new DataInputStream(new ByteArrayInputStream(value.getBytes(StandardCharsets.ISO_8859_1)))) {
+ while (dis.available() > 0) {
+ StackFrameItem item = null;
+ if (dis.readBoolean()) {
+ String className = dis.readUTF();
+ String methodName = dis.readUTF();
+ int line = dis.readInt();
+ Location location = DebuggerUtilsEx.findOrCreateLocation(vm.getVirtualMachine(), className, methodName, line);
+ item = new StackFrameItem(location, null);
}
- return res;
- }
- catch (Exception e) {
- DebuggerUtilsImpl.logError(e);
+ res.add(item);
}
+ return res;
+ }
+ catch (Exception e) {
+ DebuggerUtilsImpl.logError(e);
}
return null;
}
@@ -363,6 +368,9 @@ public final class AsyncStacksUtils {
if (!Registry.is("debugger.async.stack.trace.for.exceptions.printing", false)) {
parametersList.addProperty("debugger.agent.support.throwable", "false");
}
+ if (Registry.is("debugger.async.stack.trace.for.all.threads")) {
+ parametersList.addProperty("debugger.async.stack.trace.for.all.threads", "true");
+ }
}
}
else {
diff --git a/java/java-runtime/src/com/intellij/rt/debugger/coroutines/CoroutinesDebugHelper.java b/java/java-runtime/src/com/intellij/rt/debugger/coroutines/CoroutinesDebugHelper.java
index d985c4118e05..97ba8dc77604 100644
--- a/java/java-runtime/src/com/intellij/rt/debugger/coroutines/CoroutinesDebugHelper.java
+++ b/java/java-runtime/src/com/intellij/rt/debugger/coroutines/CoroutinesDebugHelper.java
@@ -17,10 +17,12 @@ public final class CoroutinesDebugHelper {
private static final String COROUTINE_CONTEXT_FQN = "kotlin.coroutines.CoroutineContext";
private static final String COROUTINE_JOB_FQN = "kotlinx.coroutines.Job";
private static final String COROUTINE_CONTEXT_KEY_FQN = "kotlin.coroutines.CoroutineContext$Key";
+ private static final String DEBUGGER_AGENT_CAPTURE_STORAGE_FQN = "com.intellij.rt.debugger.agent.CaptureStorage";
- public static long[] getCoroutinesRunningOnCurrentThread(Object debugProbes, Thread currentThread) throws ReflectiveOperationException {
+ public static long[] getCoroutinesRunningOnCurrentThread(Class> debugProbesImplClass, Thread currentThread) throws ReflectiveOperationException {
+ Object debugProbesImplInstance = debugProbesImplClass.getField("INSTANCE").get(null);
List coroutinesIds = new ArrayList<>();
- List infos = (List)invoke(debugProbes, "dumpCoroutinesInfo");
+ List infos = (List)invoke(debugProbesImplInstance, "dumpCoroutinesInfo");
for (Object info : infos) {
if (invoke(info, "getLastObservedThread") == currentThread) {
coroutinesIds.add((Long)invoke(info, "getSequenceNumber"));
@@ -154,10 +156,8 @@ public final class CoroutinesDebugHelper {
return current.getClass().getSimpleName().contains(COROUTINE_OWNER_CLASS);
}
- public static Object[] dumpCoroutinesInfoAsJsonAndReferences() throws ReflectiveOperationException {
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ public static Object[] dumpCoroutinesInfoAsJsonAndReferences(Class> debugProbesImplClass) {
try {
- Class> debugProbesImplClass = classLoader.loadClass("kotlinx.coroutines.debug.internal.DebugProbesImpl");
Object debugProbesImplInstance = debugProbesImplClass.getField("INSTANCE").get(null);
Object[] infos = (Object[])invoke(debugProbesImplInstance, "dumpCoroutinesInfoAsJsonAndReferences");
return infos;
@@ -166,10 +166,8 @@ public final class CoroutinesDebugHelper {
}
}
- public static Object[] dumpCoroutinesWithStacktracesAsJson() throws ReflectiveOperationException {
- ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
+ public static Object[] dumpCoroutinesWithStacktracesAsJson(Class> debugProbesImplClass) {
try {
- Class> debugProbesImplClass = classLoader.loadClass("kotlinx.coroutines.debug.internal.DebugProbesImpl");
Object debugProbesImplInstance = debugProbesImplClass.getField("INSTANCE").get(null);
Object[] dump = (Object[])invoke(debugProbesImplInstance, "dumpCoroutinesInfoAsJsonAndReferences");
Object[] coroutineInfos = (Object[])dump[3];
@@ -177,19 +175,50 @@ public final class CoroutinesDebugHelper {
for (int i = 0; i < coroutineInfos.length; i++) {
lastObservedStackTraces[i] = lastObservedStackTrace(coroutineInfos[i]);
}
- dump = Arrays.copyOf(dump, dump.length + 1);
+ dump = Arrays.copyOf(dump, dump.length + 2);
dump[4] = lastObservedStackTraces;
+
+ Object[] lastObservedThreads = (Object[])dump[1];
+ dump[5] = getAsyncStackTracesForThreads(lastObservedThreads);
return dump;
} catch (Throwable e) {
return null;
}
}
- public static String lastObservedStackTrace(Object debugCoroutineInfo) throws ReflectiveOperationException {
+ private static String lastObservedStackTrace(Object debugCoroutineInfo) throws ReflectiveOperationException {
List stackTrace = (List)invoke(debugCoroutineInfo, "lastObservedStackTrace");
return JsonUtils.dumpStackTraceElements(stackTrace);
}
+ /**
+ * Invokes com.intellij.rt.debugger.agent.CaptureStorage#getAllCapturedStacks method
+ * which returns a map of threads to their captured async stack traces.
+ * If `debugger.async.stack.trace.for.all.threads` is false, only the current thread's stack trace is returned.
+ *
+ * If debugger-agent is not available, e.g. in attach, returns null
+ */
+ private static String[] getAsyncStackTracesForThreads(Object[] threads) {
+ try {
+ Class> captureStorageClass = Class.forName(DEBUGGER_AGENT_CAPTURE_STORAGE_FQN, false, null);
+ Method getAllCapturedStacks = captureStorageClass.getMethod("getAllCapturedStacks", int.class);
+
+ Map threadToStackTrace = (Map)invoke(null, getAllCapturedStacks, 500);
+
+ String[] asyncStackTraces = new String[threads.length];
+
+ for (int i = 0; i < threads.length; i++) {
+ Object thread = threads[i];
+ if (thread != null) {
+ asyncStackTraces[i] = threadToStackTrace.get(thread);
+ }
+ }
+ return asyncStackTraces;
+ } catch (Throwable e) {
+ return null;
+ }
+ }
+
/**
* This method takes the array of {@link kotlinx.coroutines.debug.internal.DebugCoroutineInfo} instances
* and for each coroutine finds it's job and the first parent, which corresponds to some coroutine, captured in the dump.
diff --git a/java/testFramework/src/com/intellij/debugger/impl/OutputChecker.java b/java/testFramework/src/com/intellij/debugger/impl/OutputChecker.java
index 981161089258..956eca9b1222 100644
--- a/java/testFramework/src/com/intellij/debugger/impl/OutputChecker.java
+++ b/java/testFramework/src/com/intellij/debugger/impl/OutputChecker.java
@@ -262,6 +262,7 @@ public class OutputChecker {
result = result.replace("-Ddebugger.agent.enable.coroutines=true ", "");
result = result.replace("-Dkotlinx.coroutines.debug.enable.flows.stack.trace=true ", "");
result = result.replace("-Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.trace=true ", "");
+ result = result.replace("-Ddebugger.async.stack.trace.for.all.threads=true ", "");
result = result.replace("-Ddebugger.agent.support.throwable=false ", "");
result = result.replaceAll("\\((.*):\\d+\\)", "($1:!LINE_NUMBER!)");
diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt
index 9747234ff313..911cbe271fc0 100644
--- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt
+++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/CoroutineStackFrameInterceptor.kt
@@ -84,17 +84,18 @@ private class CoroutineStackFrameInterceptor : StackFrameInterceptor {
if (continuationFilter != null) return continuationFilter
// If continuation could not be extracted or the root continuation was not an instance of BaseContinuationImpl,
// dump coroutines running on the current thread and compute [CoroutineIdFilter].
- val debugProbesImpl = DebugProbesImpl.instance(defaultExecutionContext)
- return if (debugProbesImpl != null && debugProbesImpl.isInstalled) {
- // first try the helper, it is the fastest way, then try the mirror
- val currentCoroutines = getCoroutinesRunningOnCurrentThreadFromHelper(defaultExecutionContext, debugProbesImpl)
- ?: debugProbesImpl.getCoroutinesRunningOnCurrentThread(defaultExecutionContext)
-
- if (currentCoroutines.isNotEmpty()) CoroutineIdFilter(currentCoroutines)
- else null
+ val currentCoroutines = getCoroutinesRunningOnCurrentThreadFromHelper(defaultExecutionContext)
+ ?: run {
+ val debugProbesImpl = DebugProbesImpl.instance(defaultExecutionContext)
+ if (debugProbesImpl != null && debugProbesImpl.isInstalled) {
+ debugProbesImpl.getCoroutinesRunningOnCurrentThread(defaultExecutionContext)
+ } else null
+ }
+ return if (currentCoroutines != null) {
+ if (currentCoroutines.isNotEmpty()) CoroutineIdFilter(currentCoroutines) else null
} else {
//TODO: IDEA-341142 show nice notification about this
- thisLogger().warn("[coroutine filter]: kotlinx-coroutines debug agent was not enabled, DebugProbesImpl class is not found.")
+ thisLogger().warn("[coroutine filter]: kotlinx-coroutines debug agent was not enabled or DebugProbesImpl class is not found.")
null
}
}
@@ -181,11 +182,11 @@ private class CoroutineStackFrameInterceptor : StackFrameInterceptor {
}
private fun getCoroutinesRunningOnCurrentThreadFromHelper(
- context: DefaultExecutionContext,
- debugProbesImpl: DebugProbesImpl
+ context: DefaultExecutionContext
): Set? {
val threadReferenceProxyImpl = context.suspendContext.thread ?: return null
- val args = listOf(debugProbesImpl.getObject(), threadReferenceProxyImpl.threadReference)
+ val debugProbesImplClass = context.vm.findDebugProbesImplClass() ?: return null
+ val args = listOf(debugProbesImplClass, threadReferenceProxyImpl.threadReference)
val result = callMethodFromHelper(CoroutinesDebugHelper::class.java, context, "getCoroutinesRunningOnCurrentThread", args)
result ?: return null
return (result as ArrayReference).values.asSequence().map { (it as LongValue).value() }.toHashSet()
diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineInfoDatas.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineInfoDatas.kt
index 927890a4fd77..8f7954f63015 100644
--- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineInfoDatas.kt
+++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/data/coroutineInfoDatas.kt
@@ -29,7 +29,8 @@ open class CoroutineInfoData(
val lastObservedThread: ThreadReference?,
val debugCoroutineInfoRef: ObjectReference?,
private val stackFrameProvider: CoroutineStackFramesProvider?,
- val lastObservedStackTrace: List = emptyList()
+ val lastObservedStackTrace: List = emptyList(),
+ val asyncStackTrace: List = emptyList()
) {
val name: String = name ?: DEFAULT_COROUTINE_NAME
diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineInfoProvider.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineInfoProvider.kt
index 5e3affeed9b8..f97a62de1ff2 100644
--- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineInfoProvider.kt
+++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/proxy/CoroutineInfoProvider.kt
@@ -3,6 +3,8 @@
package org.jetbrains.kotlin.idea.debugger.coroutine.proxy
import com.google.gson.Gson
+import com.intellij.debugger.engine.AsyncStacksUtils
+import com.intellij.openapi.util.registry.Registry
import com.intellij.rt.debugger.JsonUtils
import com.intellij.rt.debugger.coroutines.CoroutinesDebugHelper
import com.sun.jdi.ArrayReference
@@ -15,6 +17,7 @@ import org.jetbrains.kotlin.idea.debugger.base.util.evaluate.DefaultExecutionCon
import org.jetbrains.kotlin.idea.debugger.coroutine.callMethodFromHelper
import org.jetbrains.kotlin.idea.debugger.coroutine.data.*
import org.jetbrains.kotlin.idea.debugger.coroutine.proxy.mirror.*
+import org.jetbrains.kotlin.idea.debugger.coroutine.util.findDebugProbesImplClass
import org.jetbrains.kotlin.idea.debugger.coroutine.util.logger
import org.jetbrains.kotlin.utils.addToStdlib.safeAs
import kotlin.reflect.typeOf
@@ -29,8 +32,8 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider(
private val stackFramesProvider = CoroutineStackFramesProvider(executionContext)
override fun dumpCoroutinesInfo(): List? {
-
- val array = callMethodFromHelper(CoroutinesDebugHelper::class.java, executionContext, "dumpCoroutinesInfoAsJsonAndReferences", emptyList())
+ val debugProbesImplClass = executionContext.vm.findDebugProbesImplClass() ?: return null
+ val array = callMethodFromHelper(CoroutinesDebugHelper::class.java, executionContext, "dumpCoroutinesInfoAsJsonAndReferences", listOf(debugProbesImplClass))
?: fallbackToOldMirrorDump(executionContext)
val arrayValues = (array as? ArrayReference)?.values ?: return null
@@ -55,16 +58,21 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider(
error("Arrays must have equal sizes")
}
- return calculateCoroutineInfoData(coroutinesInfo, coroutineInfoRefs, lastObservedThreadRefs, lastObservedFrameRefs, null)
+ return calculateCoroutineInfoData(coroutinesInfo, coroutineInfoRefs, lastObservedThreadRefs, lastObservedFrameRefs, null, null)
}
fun dumpCoroutinesWithStacktraces(): List? {
- val array = callMethodFromHelper(CoroutinesDebugHelper::class.java, executionContext, "dumpCoroutinesWithStacktracesAsJson", emptyList(), JsonUtils::class.java.name)
-
+ val debugProbesImplClass = executionContext.vm.findDebugProbesImplClass() ?: return null
+ val array = callMethodFromHelper(
+ CoroutinesDebugHelper::class.java, executionContext,
+ "dumpCoroutinesWithStacktracesAsJson",
+ listOf(debugProbesImplClass),
+ JsonUtils::class.java.name
+ )
val arrayValues = (array as? ArrayReference)?.values ?: return null
- if (arrayValues.size != 5) {
- error("The result array of 'dumpCoroutinesWithStacktracesAsJson' should be of size 5")
+ if (arrayValues.size != 6) {
+ error("The result array of 'dumpCoroutinesWithStacktracesAsJson' should be of size 6")
}
val coroutinesInfoAsJsonString = arrayValues[0].safeAs()?.value()
@@ -77,22 +85,45 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider(
?: error("The 4th element of the result array must be an array")
val lastObservedStackTraceJsons = arrayValues[4].safeAs()?.toTypedList()
?: error("The 5th element of the result array must be an array")
+ val asyncStackTraceJsons = arrayValues[5].safeAs()?.toTypedList()
val coroutinesInfo = Gson().fromJson(coroutinesInfoAsJsonString, Array::class.java)
val lastObservedStackTraces: List> = lastObservedStackTraceJsons.map {
- Gson().fromJson(it.value(), Array::class.java).map { ste ->
- findOrCreateLocation(executionContext, ste.stackTraceElement())
+ Gson().fromJson(it.value(), Array::class.java)
+ .map { ste ->
+ findOrCreateLocation(executionContext, ste.stackTraceElement())
+ }
+ }
+
+ val asyncStackTraces: List>? = asyncStackTraceJsons?.map { stackTrace ->
+ if (stackTrace == null) emptyList()
+ else {
+ AsyncStacksUtils.parseAgentAsyncStackTrace(stackTrace.value(), executionContext.vm)
+ .mapNotNull { it?.location() }
}
}
+ if (Registry.`is`("debugger.async.stack.trace.for.all.threads") && asyncStackTraces == null) {
+ log.error("Could not obtain async stack traces, suspendContext = ${executionContext.suspendContext}")
+ }
+
if (lastObservedStackTraces.size != lastObservedFrameRefs.size ||
lastObservedFrameRefs.size != coroutinesInfo.size ||
coroutineInfoRefs.size != coroutinesInfo.size ||
- coroutinesInfo.size != lastObservedThreadRefs.size) {
+ coroutinesInfo.size != lastObservedThreadRefs.size ||
+ (asyncStackTraces != null && asyncStackTraces.size != coroutinesInfo.size)
+ ) {
error("Arrays must have equal sizes")
}
- return calculateCoroutineInfoData(coroutinesInfo, coroutineInfoRefs, lastObservedThreadRefs, lastObservedFrameRefs, lastObservedStackTraces)
+ return calculateCoroutineInfoData(
+ coroutinesInfo,
+ coroutineInfoRefs,
+ lastObservedThreadRefs,
+ lastObservedFrameRefs,
+ lastObservedStackTraces,
+ asyncStackTraces
+ )
}
private fun fallbackToOldMirrorDump(executionContext: DefaultExecutionContext): ArrayReference? {
@@ -107,7 +138,8 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider(
coroutineInfoRefs: List,
lastObservedThreadRefs: List,
lastObservedFrameRefs: List,
- lastObservedStackTraces: List>?
+ lastObservedStackTraces: List>?,
+ asyncStackTraces: List>?
): List {
return coroutineInfoRefs.mapIndexed { i, ref ->
val info = coroutineInfos[i]
@@ -120,7 +152,8 @@ internal class CoroutinesInfoFromJsonAndReferencesProvider(
lastObservedThread = lastObservedThreadRefs[i],
debugCoroutineInfoRef = ref,
stackFrameProvider = stackFramesProvider,
- lastObservedStackTrace = lastObservedStackTraces?.get(i) ?: emptyList()
+ lastObservedStackTrace = lastObservedStackTraces?.get(i) ?: emptyList(),
+ asyncStackTrace = asyncStackTraces?.get(i) ?: emptyList()
)
}
}
diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineUtils.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineUtils.kt
index 66365e056b75..25f7df9fb50f 100644
--- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineUtils.kt
+++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/util/CoroutineUtils.kt
@@ -9,6 +9,7 @@ import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.jdi.StackFrameProxyImpl
import com.intellij.debugger.jdi.ThreadReferenceProxyImpl
+import com.intellij.debugger.jdi.VirtualMachineProxyImpl
import com.intellij.openapi.application.ReadAction
import com.intellij.openapi.diagnostic.thisLogger
import com.intellij.xdebugger.XSourcePosition
@@ -20,6 +21,7 @@ import org.jetbrains.kotlin.idea.util.application.isUnitTestMode
const val CREATION_STACK_TRACE_SEPARATOR = "\b\b\b" // the "\b\b\b" is used as creation stacktrace separator in kotlinx.coroutines
const val CREATION_CLASS_NAME = "_COROUTINE._CREATION"
+private const val DEBUG_PROBES_IMPL_CLASS_FQNAME = "kotlinx.coroutines.debug.internal.DebugProbesImpl"
fun Method.isInvokeSuspend(): Boolean =
name() == KotlinDebuggerConstants.INVOKE_SUSPEND_METHOD_NAME && signature() == "(Ljava/lang/Object;)Ljava/lang/Object;"
@@ -145,3 +147,6 @@ fun Location.isFilterFromTop(location: Location?): Boolean =
fun Location.isFilterFromBottom(location: Location?): Boolean =
sameLineAndMethod(location)
+
+internal fun VirtualMachineProxyImpl.findDebugProbesImplClass(): ClassObjectReference? =
+ classesByName(DEBUG_PROBES_IMPL_CLASS_FQNAME).firstOrNull()?.classObject()
\ No newline at end of file
diff --git a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutinesDumpAsyncProvider.kt b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutinesDumpAsyncProvider.kt
index f576079ecc92..d008d569f432 100644
--- a/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutinesDumpAsyncProvider.kt
+++ b/plugins/kotlin/jvm-debugger/coroutines/src/org/jetbrains/kotlin/idea/debugger/coroutine/view/CoroutinesDumpAsyncProvider.kt
@@ -61,12 +61,22 @@ private class CoroutineDumpItem(info: CoroutineInfoData) : MergeableDumpItem {
private val dispatcher = info.dispatcher
+ private val lastObservedStackTrace: String = info.lastObservedStackTrace.joinToString(prefix = "\t", separator = "\n\t") {
+ ThreadDumpAction.renderLocation(it)
+ }
+
override val stackTrace: String =
- info.coroutineDescriptor + "\n" +
- info.lastObservedStackTrace.joinToString(prefix = "\t", separator = "\n\t") { ThreadDumpAction.renderLocation(it) }
+ buildString {
+ appendLine(info.coroutineDescriptor)
+ appendLine(lastObservedStackTrace)
+ if (info.asyncStackTrace.isNotEmpty()) {
+ appendLine("\t--------- Async Stack Trace ---------")
+ appendLine(info.asyncStackTrace.joinToString(prefix = "\t", separator = "\n\t") { ThreadDumpAction.renderLocation(it) })
+ }
+ }
override val interestLevel: Int = when {
- info.lastObservedStackTrace.isEmpty() -> -10
+ stackTrace.isEmpty() -> -10
else -> stackTrace.count { it == '\n' }
}