From 0729b2a2302ecca876bf86537c0aeeb364b920cc Mon Sep 17 00:00:00 2001 From: Alexey Merkulov Date: Thu, 19 Sep 2024 19:05:12 +0200 Subject: [PATCH] [debugger] IDEA-358626 Show indicator with possibility to cancel the stepping request IJ-MR-145237 GitOrigin-RevId: 4283b9faf567bdbcaf25a44319928e102e3a7b10 --- .../debugger/engine/DebugProcessImpl.java | 15 +++- .../engine/DebuggerManagerThreadImpl.kt | 29 +++++-- .../debugger/engine/LightOrRealThreadInfo.kt | 5 ++ .../engine/SteppingProgressTracker.kt | 78 +++++++++++++++++++ .../debugger/impl/DebuggerSession.java | 2 +- .../messages/JavaDebuggerBundle.properties | 3 + java/java-impl/src/META-INF/JavaPlugin.xml | 2 + .../KotlinDebuggerCoreBundle.properties | 5 +- .../core/stepping/CoroutineJobInfo.kt | 3 + .../CoroutineStackFrameInterceptor.kt | 16 +++- 10 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 java/debugger/impl/src/com/intellij/debugger/engine/SteppingProgressTracker.kt diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java index 0b983cbb895f..8e827915b550 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java @@ -156,6 +156,8 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb final ThreadBlockedMonitor myThreadBlockedMonitor = new ThreadBlockedMonitor(this, disposable); + final SteppingProgressTracker mySteppingProgressTracker = new SteppingProgressTracker(this); + // These 2 fields are needs to switching from found suspend-thread context to user-friendly suspend-all context. // The main related logic is in [SuspendOtherThreadsRequestor]. volatile ParametersForSuspendAllReplacing myParametersForSuspendAllReplacing = null; @@ -197,6 +199,7 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb DebuggerStatistics.logProcessStatistics(process); } }); + mySteppingProgressTracker.installListeners(); } private DebuggerManagerThreadImpl createManagerThread() { @@ -2262,7 +2265,10 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb } private class PauseCommand extends DebuggerCommandImpl { - PauseCommand() { + @Nullable private final ThreadReferenceProxyImpl myPredefinedThread; + + PauseCommand(@Nullable ThreadReferenceProxyImpl thread) { + myPredefinedThread = thread; } @Override @@ -2274,6 +2280,9 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb getVirtualMachineProxy().suspend(); logThreads(); SuspendContextImpl suspendContext = mySuspendManager.pushSuspendContext(EventRequest.SUSPEND_ALL, 0); + if (myPredefinedThread != null) { + suspendContext.setThread(myPredefinedThread.getThreadReference()); + } myDebugProcessListeners.forEach(it -> it.paused(suspendContext)); myDebuggerManagerThread.schedule(new SuspendContextCommandImpl(suspendContext) { @@ -2697,8 +2706,8 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb } @NotNull - public DebuggerCommandImpl createPauseCommand() { - return new PauseCommand(); + public DebuggerCommandImpl createPauseCommand(@Nullable ThreadReferenceProxyImpl threadProxy) { + return new PauseCommand(threadProxy); } @NotNull diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/DebuggerManagerThreadImpl.kt b/java/debugger/impl/src/com/intellij/debugger/engine/DebuggerManagerThreadImpl.kt index 104087921b3b..70d7b728cf93 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebuggerManagerThreadImpl.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebuggerManagerThreadImpl.kt @@ -7,11 +7,7 @@ import com.intellij.debugger.engine.events.SuspendContextCommandImpl import com.intellij.debugger.engine.managerThread.DebuggerCommand import com.intellij.debugger.engine.managerThread.DebuggerManagerThread import com.intellij.debugger.engine.managerThread.SuspendContextCommand -import com.intellij.debugger.impl.DebuggerCompletableFuture -import com.intellij.debugger.impl.DebuggerUtilsAsync -import com.intellij.debugger.impl.InvokeAndWaitThread -import com.intellij.debugger.impl.InvokeThread -import com.intellij.debugger.impl.PrioritizedTask +import com.intellij.debugger.impl.* import com.intellij.openapi.Disposable import com.intellij.openapi.application.ApplicationManager import com.intellij.openapi.components.ComponentManagerEx @@ -21,16 +17,19 @@ import com.intellij.openapi.progress.util.ProgressIndicatorListener import com.intellij.openapi.progress.util.ProgressWindow import com.intellij.openapi.project.Project import com.intellij.openapi.util.Disposer +import com.intellij.openapi.util.NlsContexts.ProgressTitle +import com.intellij.platform.ide.progress.withBackgroundProgress import com.intellij.platform.util.coroutines.childScope +import com.intellij.platform.util.progress.withProgressText import com.intellij.util.concurrency.AppExecutorUtil import com.sun.jdi.VMDisconnectedException import kotlinx.coroutines.* import org.jetbrains.annotations.ApiStatus +import org.jetbrains.annotations.Nls import org.jetbrains.annotations.TestOnly import java.util.* import java.util.concurrent.CompletableFuture import java.util.concurrent.TimeUnit -import kotlin.concurrent.Volatile class DebuggerManagerThreadImpl(parent: Disposable, private val parentScope: CoroutineScope) : InvokeAndWaitThread(), DebuggerManagerThread, Disposable { @@ -53,6 +52,24 @@ class DebuggerManagerThreadImpl(parent: Disposable, private val parentScope: Cor myDisposed = true } + @ApiStatus.Internal + fun makeCancelable(project: Project, progressTitle: @ProgressTitle String, progressText: @Nls String, howToCancel: () -> Unit): CompletableDeferred { + val deferred = CompletableDeferred() + coroutineScope.launch { + withBackgroundProgress(project, progressTitle) { + withProgressText(progressText) { + try { + deferred.await() + } catch (e: CancellationException) { + howToCancel() + throw e + } + } + } + } + return deferred + } + private fun createScope() = parentScope.childScope("DebuggerManagerThreadImpl") override fun invokeAndWait(managerCommand: DebuggerCommandImpl) { diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/LightOrRealThreadInfo.kt b/java/debugger/impl/src/com/intellij/debugger/engine/LightOrRealThreadInfo.kt index 1c3157367646..2fb7d90b1f30 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/LightOrRealThreadInfo.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/LightOrRealThreadInfo.kt @@ -1,16 +1,21 @@ // Copyright 2000-2023 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license. package com.intellij.debugger.engine +import com.intellij.debugger.JavaDebuggerBundle import com.sun.jdi.ThreadReference interface LightOrRealThreadInfo { val realThread: ThreadReference? fun checkSameThread(thread: ThreadReference, suspendContext: SuspendContextImpl): Boolean + + val filterName: String } data class RealThreadInfo(override val realThread: ThreadReference) : LightOrRealThreadInfo { override fun checkSameThread(thread: ThreadReference, suspendContext: SuspendContextImpl): Boolean { return realThread == thread } + + override val filterName: String get() = JavaDebuggerBundle.message("stepping.filter.real.thread.name", realThread.name()) } diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/SteppingProgressTracker.kt b/java/debugger/impl/src/com/intellij/debugger/engine/SteppingProgressTracker.kt new file mode 100644 index 000000000000..f9950b427917 --- /dev/null +++ b/java/debugger/impl/src/com/intellij/debugger/engine/SteppingProgressTracker.kt @@ -0,0 +1,78 @@ +// 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.engine + +import com.intellij.debugger.JavaDebuggerBundle +import com.intellij.debugger.engine.events.DebuggerCommandImpl +import com.intellij.debugger.engine.jdi.ThreadReferenceProxy +import com.intellij.debugger.jdi.ThreadReferenceProxyImpl +import com.sun.jdi.request.EventRequest +import kotlinx.coroutines.CompletableDeferred +import org.jetbrains.annotations.Nls + +private data class TrackedSteppingData(val stepCompetedStatus: CompletableDeferred, val threadFilter: (ThreadReferenceProxy?) -> Boolean) + +private class SteppingProgressTracker(private val debuggerProcessImpl: DebugProcessImpl) { + private val trackedStepping = mutableListOf() + + fun installListeners() { + debuggerProcessImpl.addDebugProcessListener(object : DebugProcessListener { + override fun paused(suspendContext: SuspendContext) { + val thread = suspendContext.thread + val completedSteps = trackedStepping.filter { it.threadFilter(thread) } + for ((stepCompetedStatus, _) in completedSteps) { + stepCompetedStatus.complete(Unit) + } + trackedStepping.removeAll(completedSteps) + } + }) + } + + fun addStepping(stepCompetedStatus: CompletableDeferred, threadFilter: (ThreadReferenceProxy?) -> Boolean) { + trackedStepping.add(TrackedSteppingData(stepCompetedStatus, threadFilter)) + } +} + +private class CancelingSteppingListener : SteppingListener { + override fun beforeSteppingStarted(suspendContext: SuspendContextImpl, steppingAction: SteppingAction) { + val debuggerProcessImpl: DebugProcessImpl = suspendContext.debugProcess + val filter: LightOrRealThreadInfo? = debuggerProcessImpl.requestsManager.filterThread + + val threadForStepping: ThreadReferenceProxyImpl? = + if (filter != null) debuggerProcessImpl.virtualMachineProxy.getThreadReferenceProxy(filter.realThread) + else suspendContext.thread + + val needSuspendOnlyThread = suspendContext.suspendPolicy == EventRequest.SUSPEND_EVENT_THREAD && threadForStepping != null + + val whereStr = + if (threadForStepping != null) JavaDebuggerBundle.message("stepping.filter.real.thread.name", threadForStepping.name()) + else filter?.filterName ?: debuggerProcessImpl.session.sessionName + + val steppingRestrictionMessage = getSteppingRestrictionMessage(whereStr, steppingAction) + val steppingName = steppingAction.steppingName + val stepCompetedStatus = debuggerProcessImpl.managerThread.makeCancelable(debuggerProcessImpl.project, steppingRestrictionMessage, steppingName) { + val command: DebuggerCommandImpl = + if (needSuspendOnlyThread) debuggerProcessImpl.createFreezeThreadCommand(threadForStepping) + else debuggerProcessImpl.createPauseCommand(threadForStepping) + debuggerProcessImpl.managerThread.schedule(command) + } + + val tracker = suspendContext.debugProcess.mySteppingProgressTracker + tracker.addStepping(stepCompetedStatus) { thread -> + !needSuspendOnlyThread || thread == null || thread == threadForStepping + } + } +} + +private val SteppingAction.steppingName: @Nls String get() = when (this) { + SteppingAction.STEP_INTO -> JavaDebuggerBundle.message("status.step.into") + SteppingAction.STEP_OUT -> JavaDebuggerBundle.message("status.step.out") + SteppingAction.STEP_OVER -> JavaDebuggerBundle.message("status.step.over") + SteppingAction.RUN_TO_CURSOR -> JavaDebuggerBundle.message("status.run.to.cursor") +} + +private fun getSteppingRestrictionMessage(whereSteppingIsPerformed: @Nls String?, steppingAction: SteppingAction): @Nls String { + return when (steppingAction) { + SteppingAction.RUN_TO_CURSOR -> JavaDebuggerBundle.message("status.run.to.cursor.in", whereSteppingIsPerformed) + else -> JavaDebuggerBundle.message("status.stepping.in", whereSteppingIsPerformed) + } +} diff --git a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerSession.java b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerSession.java index 9eb0c74f8739..44dc9492ca6a 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerSession.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerSession.java @@ -409,7 +409,7 @@ public final class DebuggerSession implements AbstractDebuggerSession { } public void pause() { - myDebugProcess.getManagerThread().schedule(myDebugProcess.createPauseCommand()); + myDebugProcess.getManagerThread().schedule(myDebugProcess.createPauseCommand(null)); } /*Presentation*/ diff --git a/java/debugger/openapi/resources/messages/JavaDebuggerBundle.properties b/java/debugger/openapi/resources/messages/JavaDebuggerBundle.properties index ff4f173d416e..5902cc6dc29f 100644 --- a/java/debugger/openapi/resources/messages/JavaDebuggerBundle.properties +++ b/java/debugger/openapi/resources/messages/JavaDebuggerBundle.properties @@ -95,6 +95,8 @@ status.step.out=Stepping out status.step.into=Stepping into status.step.over=Stepping over status.run.to.cursor=Run to cursor +status.run.to.cursor.in=Run to cursor in {0} +status.stepping.in=Stepping in {0} status.process.resumed=Process resumed status.paused.in.another.thread=Paused in another thread string.connection=''{0}'', transport: ''{1}'' @@ -548,3 +550,4 @@ paths.to.closest.gc.roots.for=Paths to the Closest GC Roots for {0} column.name.instances=Instances column.name.shallow.size=Shallow Size column.name.retained.size=Retained Size +stepping.filter.real.thread.name=thread {0} diff --git a/java/java-impl/src/META-INF/JavaPlugin.xml b/java/java-impl/src/META-INF/JavaPlugin.xml index 117e2563086e..5137f2f34fc5 100644 --- a/java/java-impl/src/META-INF/JavaPlugin.xml +++ b/java/java-impl/src/META-INF/JavaPlugin.xml @@ -2883,6 +2883,8 @@ +