From 2ad7f181aef04085e9bfa733ec81eec47b040c05 Mon Sep 17 00:00:00 2001 From: "Alexey.Merkulov" Date: Tue, 14 Oct 2025 21:20:21 +0200 Subject: [PATCH] [debugger] Fix stepping in several threads in suspend-thread mode IDEA-381878 IDEA-365578 GitOrigin-RevId: 777ff16c86ef62b4f52326fca41768d5b064dbf5 --- .../debugger/engine/DebugProcessImpl.java | 9 ++++-- .../debugger/engine/JavaDebugProcess.java | 5 ++-- .../engine/JavaSteppingSuspendContext.kt | 2 +- .../engine/SteppingProgressTracker.kt | 23 +++++++-------- .../debugger/impl/DebuggerSession.java | 28 +++++++++++++++---- .../ExecutionWithDebuggerToolsTestCase.java | 2 +- 6 files changed, 46 insertions(+), 23 deletions(-) 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 f00f402b4a3c..d5474e234ce2 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/DebugProcessImpl.java @@ -163,7 +163,8 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb final ThreadBlockedMonitor myThreadBlockedMonitor = new ThreadBlockedMonitor(this, disposable); - final SteppingProgressTracker mySteppingProgressTracker = new SteppingProgressTracker(this); + @ApiStatus.Internal + public final SteppingProgressTracker mySteppingProgressTracker = new SteppingProgressTracker(this); protected final @NotNull RunToCursorManager myRunToCursorManager; @@ -215,7 +216,6 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb DebuggerStatistics.logProcessStatistics(process); } }); - mySteppingProgressTracker.installListeners(); } private DebuggerManagerThreadImpl createManagerThread() { @@ -2989,6 +2989,11 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb } } + @ApiStatus.Internal + public boolean isSteppingInProgress() { + return mySteppingProgressTracker.isSteppingInProgress(); + } + void stopWatchingMethodReturn() { if (myReturnValueWatcher != null) { myReturnValueWatcher.disable(); diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java b/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java index a7707c960944..23611ccdc39b 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java +++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaDebugProcess.java @@ -118,12 +118,13 @@ public class JavaDebugProcess extends XDebugProcess { if (event == DebuggerSession.Event.CONTEXT) { DebuggerSession debuggerSession = newContext.getDebuggerSession(); ThreadReferenceProxyImpl steppingThreadProxy = newContext.getThreadProxy(); - if (debuggerSession != null && steppingThreadProxy != null && debuggerSession.getState() == DebuggerSession.State.IN_STEPPING) { + if (debuggerSession != null && debuggerSession.getState() == DebuggerSession.State.IN_STEPPING) { DebugProcessImpl debugProcess = debuggerSession.getProcess(); debugProcess.getManagerThread().schedule(new DebuggerCommandImpl() { @Override protected void action() { - JavaExecutionStack stack = new JavaExecutionStack(steppingThreadProxy, debugProcess, true); + JavaExecutionStack stack = steppingThreadProxy != null ? + new JavaExecutionStack(steppingThreadProxy, debugProcess, true) : null; getSession().positionReached(new JavaSteppingSuspendContext(debugProcess, stack)); } }); diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/JavaSteppingSuspendContext.kt b/java/debugger/impl/src/com/intellij/debugger/engine/JavaSteppingSuspendContext.kt index 9280ceaa8f5c..b390d8bc5da6 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/JavaSteppingSuspendContext.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/JavaSteppingSuspendContext.kt @@ -6,7 +6,7 @@ import com.intellij.xdebugger.impl.XSteppingSuspendContext internal class JavaSteppingSuspendContext( private val debugProcessImpl: DebugProcessImpl, - private val javaExecutionStack: JavaExecutionStack + private val javaExecutionStack: JavaExecutionStack? ) : XSteppingSuspendContext() { override fun getActiveExecutionStack(): XExecutionStack? { diff --git a/java/debugger/impl/src/com/intellij/debugger/engine/SteppingProgressTracker.kt b/java/debugger/impl/src/com/intellij/debugger/engine/SteppingProgressTracker.kt index e6c0ac74f7be..dc4e4c47b010 100644 --- a/java/debugger/impl/src/com/intellij/debugger/engine/SteppingProgressTracker.kt +++ b/java/debugger/impl/src/com/intellij/debugger/engine/SteppingProgressTracker.kt @@ -17,17 +17,18 @@ private data class TrackedSteppingData(val stepCompetedStatus: CompletableDeferr internal 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, suspendContext as SuspendContextImpl) } - for ((stepCompetedStatus, _) in completedSteps) { - stepCompetedStatus.complete(Unit) - } - trackedStepping.removeAll(completedSteps) - } - }) + val isSteppingInProgress: Boolean get() = trackedStepping.isNotEmpty() + + /** returns true iff the [suspendContext] is the end of ongoing stepping */ + fun onPaused(suspendContext: SuspendContext): Boolean { + val thread = suspendContext.thread + val completedSteps = trackedStepping.filter { it.threadFilter(thread, suspendContext as SuspendContextImpl) } + for ((stepCompetedStatus, _) in completedSteps) { + stepCompetedStatus.complete(Unit) + } + + trackedStepping.removeAll(completedSteps) + return completedSteps.isNotEmpty() } fun addStepping(stepCompetedStatus: CompletableDeferred, threadFilter: (ThreadReferenceProxy?, SuspendContextImpl) -> Boolean) { 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 e962fbf87b14..e005fff1a198 100644 --- a/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerSession.java +++ b/java/debugger/impl/src/com/intellij/debugger/impl/DebuggerSession.java @@ -100,7 +100,13 @@ public final class DebuggerSession implements AbstractDebuggerSession { } public void setSteppingThrough(ThreadReferenceProxyImpl threadProxy) { - mySteppingThroughThread.set(threadProxy); + LightOrRealThreadInfo filterThread = myDebugProcess.getRequestsManager().getFilterThread(); + if (filterThread != null && filterThread.getRealThread() != null) { + mySteppingThroughThread.set(threadProxy); + } + else { + mySteppingThroughThread.set(null); + } } public void clearSteppingThrough() { @@ -531,9 +537,11 @@ public final class DebuggerSession implements AbstractDebuggerSession { public void paused(final SuspendContextImpl suspendContext) { LOG.debug("paused"); + boolean isSteppingEnds = myDebugProcess.mySteppingProgressTracker.onPaused(suspendContext); + ThreadReferenceProxyImpl currentThread = suspendContext.getEventThread(); - if (!shouldSetAsActiveContext(suspendContext)) { + if (!shouldSetAsActiveContext(suspendContext, isSteppingEnds)) { notifyThreadsRefresh(); ThreadReferenceProxyImpl thread = suspendContext.getEventThread(); if (thread != null) { @@ -683,9 +691,17 @@ public final class DebuggerSession implements AbstractDebuggerSession { } } - private boolean shouldSetAsActiveContext(final SuspendContextImpl suspendContext) { + private boolean shouldSetAsActiveContext(final SuspendContextImpl suspendContext, boolean isSteppingEnds) { + if (suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_ALL) { + return true; + } + + if (getProcess().getSuspendManager().getPausedContexts().size() > 1) { + return isSteppingEnds; + } + final ThreadReferenceProxyImpl newThread = suspendContext.getEventThread(); - if (newThread == null || suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_ALL || isSteppingThrough(newThread)) { + if (newThread == null || suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_ALL || !myDebugProcess.isSteppingInProgress()) { return true; } final SuspendContextImpl currentSuspendContext = getContextManager().getContext().getSuspendContext(); @@ -706,7 +722,7 @@ public final class DebuggerSession implements AbstractDebuggerSession { ThreadReferenceProxyImpl steppingThread = getSteppingThread(suspendContext); DebuggerInvocationUtil.invokeLater(getProject(), () -> { - if (steppingThread != null && context != null) { + if (myDebugProcess.isSteppingInProgress() && context != null) { switchToActiveSteppingContext(steppingThread); } else if (context != null) { @@ -784,7 +800,7 @@ public final class DebuggerSession implements AbstractDebuggerSession { } } - private void switchToActiveSteppingContext(ThreadReferenceProxyImpl steppingThread) { + private void switchToActiveSteppingContext(@Nullable ThreadReferenceProxyImpl steppingThread) { DebuggerContextImpl debuggerContext = DebuggerContextImpl.createDebuggerContext(DebuggerSession.this, null, steppingThread, null); getContextManager().setState(debuggerContext, State.IN_STEPPING, Event.CONTEXT, getDescription(debuggerContext)); } diff --git a/java/testFramework/src/com/intellij/debugger/ExecutionWithDebuggerToolsTestCase.java b/java/testFramework/src/com/intellij/debugger/ExecutionWithDebuggerToolsTestCase.java index d5a85316f1a1..d9951813a73f 100644 --- a/java/testFramework/src/com/intellij/debugger/ExecutionWithDebuggerToolsTestCase.java +++ b/java/testFramework/src/com/intellij/debugger/ExecutionWithDebuggerToolsTestCase.java @@ -624,7 +624,7 @@ public abstract class ExecutionWithDebuggerToolsTestCase extends ExecutionTestCa public void resumed(SuspendContextImpl suspendContext) { SuspendContextImpl pausedContext = myDebugProcess.getSuspendManager().getPausedContext(); // do not switch context on resume inside stepping - if (pausedContext != null && myDebugProcess.getSession().getSteppingThread(suspendContext) == null) { + if (pausedContext != null && !myDebugProcess.isSteppingInProgress()) { paused(pausedContext); } }