[debugger] Fix stepping in several threads in suspend-thread mode

IDEA-381878 IDEA-365578

GitOrigin-RevId: 777ff16c86ef62b4f52326fca41768d5b064dbf5
This commit is contained in:
Alexey.Merkulov
2025-11-08 00:55:18 +00:00
committed by intellij-monorepo-bot
parent 277042a171
commit 2ad7f181ae
6 changed files with 46 additions and 23 deletions
@@ -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();
@@ -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));
}
});
@@ -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? {
@@ -17,17 +17,18 @@ private data class TrackedSteppingData(val stepCompetedStatus: CompletableDeferr
internal class SteppingProgressTracker(private val debuggerProcessImpl: DebugProcessImpl) {
private val trackedStepping = mutableListOf<TrackedSteppingData>()
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<Unit>, threadFilter: (ThreadReferenceProxy?, SuspendContextImpl) -> Boolean) {
@@ -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));
}
@@ -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);
}
}