From a9ba23fd1159926364ed7a48eb0155bd7f97a9eb Mon Sep 17 00:00:00 2001 From: Alexander Kuznetsov Date: Thu, 1 May 2025 17:52:49 +0200 Subject: [PATCH] IJPL-186342 [rd][debugger] Async Stack Traces as a frontend action GitOrigin-RevId: 4d62f3552baf3dfa7645708b6d1c5833761c3272 --- .../backend/BackendJavaDebuggerSessionApi.kt | 6 ++ .../impl/resources/META-INF/java-debugger.xml | 4 -- .../actions/AsyncStacksToggleAction.kt | 47 --------------- .../debugger/engine/AsyncStacksUtils.java | 13 +++++ .../debugger/engine/JavaExecutionStack.java | 6 +- .../intellij.java.debugger.impl.shared.xml | 4 ++ .../JavaDebuggerSharedBundle.properties | 1 + .../impl/shared/SharedJavaDebuggerSession.kt | 1 + .../shared/actions/AsyncStacksToggleAction.kt | 57 +++++++++++++++++++ .../impl/shared/rpc/JavaDebuggerSessionApi.kt | 2 + .../src/messages/ActionsBundle.properties | 1 - .../CoroutineStackFrameInterceptor.kt | 10 +--- 12 files changed, 90 insertions(+), 62 deletions(-) delete mode 100644 java/debugger/impl/src/com/intellij/debugger/actions/AsyncStacksToggleAction.kt create mode 100644 java/debugger/shared/src/com/intellij/java/debugger/impl/shared/actions/AsyncStacksToggleAction.kt diff --git a/java/debugger/backend/src/com/intellij/java/debugger/impl/backend/BackendJavaDebuggerSessionApi.kt b/java/debugger/backend/src/com/intellij/java/debugger/impl/backend/BackendJavaDebuggerSessionApi.kt index 54678f173a4d..dfaeaa5454e8 100644 --- a/java/debugger/backend/src/com/intellij/java/debugger/impl/backend/BackendJavaDebuggerSessionApi.kt +++ b/java/debugger/backend/src/com/intellij/java/debugger/impl/backend/BackendJavaDebuggerSessionApi.kt @@ -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, maxItems: Int): Pair, Int> { diff --git a/java/debugger/impl/resources/META-INF/java-debugger.xml b/java/debugger/impl/resources/META-INF/java-debugger.xml index 135fc4ded2f2..f7a34a1c037e 100644 --- a/java/debugger/impl/resources/META-INF/java-debugger.xml +++ b/java/debugger/impl/resources/META-INF/java-debugger.xml @@ -290,10 +290,6 @@ - - - - diff --git a/java/debugger/impl/src/com/intellij/debugger/actions/AsyncStacksToggleAction.kt b/java/debugger/impl/src/com/intellij/debugger/actions/AsyncStacksToggleAction.kt deleted file mode 100644 index 5b6949f48903..000000000000 --- a/java/debugger/impl/src/com/intellij/debugger/actions/AsyncStacksToggleAction.kt +++ /dev/null @@ -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("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) - } - } -} 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 3ca2e9269f63..42159bbba80a 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/AsyncStacksUtils.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/AsyncStacksUtils.java @@ -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 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; diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java b/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java index 259ee0ac780a..f4d298216230 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaExecutionStack.java @@ -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 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, diff --git a/java/debugger/shared/resources/intellij.java.debugger.impl.shared.xml b/java/debugger/shared/resources/intellij.java.debugger.impl.shared.xml index cb74db16fa8e..2b3bce35a500 100644 --- a/java/debugger/shared/resources/intellij.java.debugger.impl.shared.xml +++ b/java/debugger/shared/resources/intellij.java.debugger.impl.shared.xml @@ -12,6 +12,10 @@ key="debugger.thread.dump.max.items.frontend"/> + + + + diff --git a/java/debugger/shared/resources/messages/JavaDebuggerSharedBundle.properties b/java/debugger/shared/resources/messages/JavaDebuggerSharedBundle.properties index ae23c6a845f6..25d512cfa2f9 100644 --- a/java/debugger/shared/resources/messages/JavaDebuggerSharedBundle.properties +++ b/java/debugger/shared/resources/messages/JavaDebuggerSharedBundle.properties @@ -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 \ No newline at end of file diff --git a/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/SharedJavaDebuggerSession.kt b/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/SharedJavaDebuggerSession.kt index 975f975de136..be758f7718cf 100644 --- a/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/SharedJavaDebuggerSession.kt +++ b/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/SharedJavaDebuggerSession.kt @@ -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() diff --git a/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/actions/AsyncStacksToggleAction.kt b/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/actions/AsyncStacksToggleAction.kt new file mode 100644 index 000000000000..6efdb759ee8a --- /dev/null +++ b/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/actions/AsyncStacksToggleAction.kt @@ -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() + } +} diff --git a/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/rpc/JavaDebuggerSessionApi.kt b/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/rpc/JavaDebuggerSessionApi.kt index c2be8216b9db..aed0a4249e27 100644 --- a/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/rpc/JavaDebuggerSessionApi.kt +++ b/java/debugger/shared/src/com/intellij/java/debugger/impl/shared/rpc/JavaDebuggerSessionApi.kt @@ -22,6 +22,8 @@ interface JavaDebuggerSessionApi : RemoteApi { 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 { diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index bdb24d77a424..653f202eea3a 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -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 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 365c8b44de58..09cd058f4170 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 @@ -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