IJPL-186342 [rd][debugger] Async Stack Traces as a frontend action

GitOrigin-RevId: 4d62f3552baf3dfa7645708b6d1c5833761c3272
This commit is contained in:
Alexander Kuznetsov
2025-05-02 10:19:21 +00:00
committed by intellij-monorepo-bot
parent 287c4ec997
commit a9ba23fd11
12 changed files with 90 additions and 62 deletions
@@ -2,6 +2,7 @@
package com.intellij.java.debugger.impl.backend
import com.intellij.debugger.actions.ThreadDumpAction
import com.intellij.debugger.engine.AsyncStacksUtils
import com.intellij.debugger.engine.JavaDebugProcess
import com.intellij.debugger.engine.executeOnDMT
import com.intellij.execution.filters.ExceptionFilters
@@ -52,6 +53,11 @@ internal class BackendJavaDebuggerSessionApi : JavaDebuggerSessionApi {
}
return JavaThreadDumpResponseDto(channelDeferred.await(), ExceptionFilters.getFilters(session.searchScope))
}
override suspend fun setAsyncStacksEnabled(sessionId: XDebugSessionId, state: Boolean) {
val session = sessionId.findValue() ?: return
AsyncStacksUtils.setAsyncStacksEnabled(session, state)
}
}
private fun truncateIfNeeded(allDumpItems: List<DumpItem>, maxItems: Int): Pair<List<DumpItem>, Int> {
@@ -290,10 +290,6 @@
<add-to-group group-id="XDebugger.ValueGroup" anchor="last"/>
</action>
<action id="Debugger.AsyncStacks" class="com.intellij.debugger.actions.AsyncStacksToggleAction">
<add-to-group group-id="XDebugger.Frames.Tree.Popup"/>
<add-to-group group-id="XDebugger.Frames.Tree.Popup.Frontend"/>
</action>
<action id="Debugger.SwitchToTheNextContext" class="com.intellij.debugger.actions.SwitchToTheNextContextAction"/>
<action id="Debugger.CallTracer" class="com.intellij.debugger.ui.breakpoints.CallTracer$CallTracerToggleAction"
icon="AllIcons.Debugger.Db_set_breakpoint">
@@ -1,47 +0,0 @@
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.debugger.actions
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.project.DumbAwareToggleAction
import com.intellij.openapi.util.Key
import com.intellij.xdebugger.impl.XDebugSessionImpl
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
class AsyncStacksToggleAction : DumbAwareToggleAction() {
override fun isSelected(e: AnActionEvent): Boolean {
return ASYNC_STACKS_ENABLED.get(DebuggerUIUtil.getSessionData(e), true)
}
override fun setSelected(e: AnActionEvent, state: Boolean) {
ASYNC_STACKS_ENABLED.set(DebuggerUIUtil.getSessionData(e), state)
DebuggerUIUtil.getSessionProxy(e)?.apply {
if (isSuspended) {
rebuildViews()
}
}
}
override fun update(e: AnActionEvent) {
super.update(e)
e.presentation.isEnabledAndVisible = DebuggerAction.isInJavaSession(e)
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}
companion object {
private val ASYNC_STACKS_ENABLED = Key.create<Boolean>("ASYNC_STACKS_ENABLED")
@JvmStatic
fun isAsyncStacksEnabled(session: XDebugSessionImpl): Boolean {
return ASYNC_STACKS_ENABLED.get(session.sessionData, true)
}
@JvmStatic
fun setAsyncStacksEnabled(session: XDebugSessionImpl, state: Boolean) {
ASYNC_STACKS_ENABLED.set(session.sessionData, state)
}
}
}
@@ -35,7 +35,9 @@ import com.intellij.platform.eel.provider.utils.EelPathUtils;
import com.intellij.util.PathUtil;
import com.intellij.util.SlowOperations;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.xdebugger.impl.XDebugSessionImpl;
import com.sun.jdi.*;
import org.jetbrains.annotations.ApiStatus;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.intellij.build.BuildDependenciesJps;
@@ -56,6 +58,17 @@ public final class AsyncStacksUtils {
public static final String CAPTURE_STORAGE_CLASS_NAME = "com.intellij.rt.debugger.agent.CaptureStorage";
public static final String CAPTURE_AGENT_CLASS_NAME = "com.intellij.rt.debugger.agent.CaptureAgent";
private static final String AGENT_JAR_NAME = "debugger-agent.jar";
private static final Key<Boolean> ASYNC_STACKS_ENABLED = Key.create("ASYNC_STACKS_ENABLED");
@ApiStatus.Internal
public static Boolean isAsyncStacksEnabled(XDebugSessionImpl session) {
return ASYNC_STACKS_ENABLED.get(session.getSessionData(), true);
}
@ApiStatus.Internal
public static void setAsyncStacksEnabled(XDebugSessionImpl session, Boolean state) {
ASYNC_STACKS_ENABLED.set(session.getSessionData(), state);
}
public static boolean isAgentEnabled() {
return DebuggerSettings.getInstance().INSTRUMENTING_AGENT;
@@ -2,7 +2,6 @@
package com.intellij.debugger.engine;
import com.intellij.debugger.JavaDebuggerBundle;
import com.intellij.debugger.actions.AsyncStacksToggleAction;
import com.intellij.debugger.engine.evaluation.EvaluateException;
import com.intellij.debugger.engine.events.DebuggerContextCommandImpl;
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
@@ -356,8 +355,9 @@ public class JavaExecutionStack extends XExecutionStack {
List<StackFrameItem> relatedStack = null;
var creationStack = myCreationStack;
XStackFrame topFrame = ContainerUtil.getFirstItem(frames);
if (AsyncStacksToggleAction.isAsyncStacksEnabled(
(XDebugSessionImpl)suspendContext.getDebugProcess().getXdebugProcess().getSession()) &&
JavaDebugProcess xdebugProcess = suspendContext.getDebugProcess().getXdebugProcess();
if (xdebugProcess != null &&
AsyncStacksUtils.isAsyncStacksEnabled((XDebugSessionImpl)xdebugProcess.getSession()) &&
topFrame instanceof JavaStackFrame frame) {
if (creationStack == null) {
creationStack = DebugUtilsKt.computeSafeIfAny(CreationStackTraceProvider.EP,
@@ -12,6 +12,10 @@
key="debugger.thread.dump.max.items.frontend"/>
</extensions>
<actions resource-bundle="messages.JavaDebuggerSharedBundle">
<action id="Debugger.AsyncStacks" class="com.intellij.java.debugger.impl.shared.actions.AsyncStacksToggleAction">
<add-to-group group-id="XDebugger.Frames.Tree.Popup"/>
<add-to-group group-id="XDebugger.Frames.Tree.Popup.Frontend"/>
</action>
<action id="DumpThreads" class="com.intellij.java.debugger.impl.shared.actions.ThreadDumpAction" icon="AllIcons.Actions.Dump">
<add-to-group group-id="DebuggingActionsGroup" anchor="last"/>
<add-to-group group-id="XDebugger.ToolWindow.TopToolbar3.Extra"/>
@@ -1,3 +1,4 @@
action.Debugger.AsyncStacks.text=Async Stack Traces
action.DumpThreads.text=Get Thread Dump
thread.dump.name=Dump {0}
thread.dump=Thread Dump
@@ -15,6 +15,7 @@ class SharedJavaDebuggerSession(dto: JavaDebuggerSessionDto, private val cs: Cor
val isAttached: Boolean get() = isAttachedFlow.value
internal var isAsyncStacksEnabled: Boolean = true
fun close() {
cs.cancel()
@@ -0,0 +1,57 @@
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.java.debugger.impl.shared.actions
import com.intellij.java.debugger.impl.shared.SharedJavaDebuggerManager
import com.intellij.java.debugger.impl.shared.SharedJavaDebuggerSession
import com.intellij.java.debugger.impl.shared.rpc.JavaDebuggerSessionApi
import com.intellij.openapi.actionSystem.ActionUpdateThread
import com.intellij.openapi.actionSystem.AnActionEvent
import com.intellij.openapi.actionSystem.remoting.ActionRemoteBehaviorSpecification
import com.intellij.openapi.components.Service
import com.intellij.openapi.components.service
import com.intellij.openapi.project.DumbAwareToggleAction
import com.intellij.openapi.project.Project
import com.intellij.xdebugger.impl.frame.XDebugSessionProxy
import com.intellij.xdebugger.impl.ui.DebuggerUIUtil
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.launch
class AsyncStacksToggleAction : DumbAwareToggleAction(), ActionRemoteBehaviorSpecification.FrontendOtherwiseBackend {
override fun isSelected(e: AnActionEvent): Boolean {
return getJavaSession(e)?.isAsyncStacksEnabled ?: true
}
override fun setSelected(e: AnActionEvent, state: Boolean) {
getJavaSession(e)?.isAsyncStacksEnabled = state
DebuggerUIUtil.getSessionProxy(e)?.apply {
AsyncStackTraceActionCoroutineScope.getInstance(project).cs.launch {
JavaDebuggerSessionApi.getInstance().setAsyncStacksEnabled(id, state)
}
if (isSuspended) {
rebuildViews()
}
}
}
override fun update(e: AnActionEvent) {
super.update(e)
e.presentation.isEnabledAndVisible = getJavaSession(e) != null
}
override fun getActionUpdateThread(): ActionUpdateThread {
return ActionUpdateThread.BGT
}
}
private fun getJavaSession(e: AnActionEvent) = DebuggerUIUtil.getSessionProxy(e)?.let(::getJavaSession)
private fun getJavaSession(proxy: XDebugSessionProxy): SharedJavaDebuggerSession? = SharedJavaDebuggerManager.getInstance(proxy.project).getJavaSession(proxy.id)
@Suppress("OPT_IN_USAGE")
@Service(Service.Level.PROJECT)
internal class AsyncStackTraceActionCoroutineScope(val cs: CoroutineScope) {
companion object {
fun getInstance(project: Project): AsyncStackTraceActionCoroutineScope = project.service()
}
}
@@ -22,6 +22,8 @@ interface JavaDebuggerSessionApi : RemoteApi<Unit> {
suspend fun dumpThreads(sessionId: XDebugSessionId, maxItems: Int = Int.MAX_VALUE, onlyPlatformThreads: Boolean): JavaThreadDumpResponseDto?
suspend fun setAsyncStacksEnabled(sessionId: XDebugSessionId, state: Boolean)
companion object {
@JvmStatic
suspend fun getInstance(): JavaDebuggerSessionApi {
@@ -319,7 +319,6 @@ action.Debugger.CreateRenderer.text=Create\u2026
action.Debugger.CreateRenderer.XDebuggerTreePopup.text=Create Renderer
action.Debugger.XDebuggerTextPopup.ShowAsObject.text=Show as Object
action.Debugger.MuteRenderers.text=Mute Renderers
action.Debugger.AsyncStacks.text=Async Stack Traces
action.Debugger.SwitchToTheNextContext.text=Switch to the Next Stopped Breakpoint
action.Debugger.CallTracer.text=Trace Calls
action.Debugger.ReloadFile.text=Compile And Reload File
@@ -2,12 +2,7 @@
package org.jetbrains.kotlin.idea.debugger.coroutine
import com.intellij.debugger.actions.AsyncStacksToggleAction
import com.intellij.debugger.engine.DebugProcessImpl
import com.intellij.debugger.engine.DebuggerManagerThreadImpl
import com.intellij.debugger.engine.MethodInvokeUtils
import com.intellij.debugger.engine.SuspendContextImpl
import com.intellij.debugger.engine.SuspendManagerUtil
import com.intellij.debugger.engine.*
import com.intellij.debugger.impl.DebuggerUtilsEx
import com.intellij.debugger.impl.DebuggerUtilsImpl
import com.intellij.debugger.impl.HelperClassNotAvailableException
@@ -41,7 +36,8 @@ private class CoroutineStackFrameInterceptor : StackFrameInterceptor {
DebuggerManagerThreadImpl.assertIsManagerThread()
if (debugProcess.xdebugProcess?.session !is XDebugSessionImpl
|| frame is SkipCoroutineStackFrameProxyImpl
|| !AsyncStacksToggleAction.isAsyncStacksEnabled(debugProcess.xdebugProcess?.session as XDebugSessionImpl)) {
|| !AsyncStacksUtils.isAsyncStacksEnabled(debugProcess.xdebugProcess?.session as XDebugSessionImpl)
) {
return null
}
// skip -1 line in invokeSuspend and main