mirror of
https://gitflic.ru/project/openide/openide.git
synced 2025-12-16 14:23:28 +07:00
[debugger] IDEA-338723 Fix suspending behavior while stepping in coroutines
Race conditions in continuation filtering in suspend-all mode force to switch to suspend-thread mode. But that behavior is different from suspend-all mode during regular stepping. To solve this problem, the workaround is implemented: found suspended thread calls suspend-all request (actually, track some method entering any thread). That next suspension resumes the original one-thead suspension and replaces the suspension to itself. So a user will just see "regular" suspend-all thread picture. Note 1: Technically, the replaced suspended thread has another "primary" thread. It may lead to some problems. Note 2: Suspend one-thread mode is working supported in coroutine stepping. But now it does not trigger thread switching (just notification is showing). GitOrigin-RevId: 90d823e0ece4b6ce3a548ad984cd6e824dc516b4
This commit is contained in:
committed by
intellij-monorepo-bot
parent
d4f36c6437
commit
8b38a4f0d4
@@ -636,12 +636,17 @@ public class DebugProcessEvents extends DebugProcessImpl {
|
||||
// Don't try to check breakpoint's condition or evaluate its log expression,
|
||||
// because these evaluations may lead to skipping of more important stepping events,
|
||||
// see IDEA-336282.
|
||||
if (!DebuggerSession.filterBreakpointsDuringSteppingUsingDebuggerEngine()) {
|
||||
boolean filterWasUsed = false;
|
||||
boolean shouldIgnoreThreadFiltering = requestor == null || !requestor.shouldIgnoreThreadFiltering();
|
||||
if (!DebuggerSession.filterBreakpointsDuringSteppingUsingDebuggerEngine() && shouldIgnoreThreadFiltering) {
|
||||
LightOrRealThreadInfo filter = getRequestsManager().getFilterThread();
|
||||
if (filter != null && !filter.checkSameThread(thread, suspendContext)) {
|
||||
notifySkippedBreakpoints(event, SkippedBreakpointReason.STEPPING);
|
||||
suspendManager.voteResume(suspendContext);
|
||||
return;
|
||||
if (filter != null) {
|
||||
if (!filter.checkSameThread(thread, suspendContext)) {
|
||||
notifySkippedBreakpoints(event, SkippedBreakpointReason.STEPPING);
|
||||
suspendManager.voteResume(suspendContext);
|
||||
return;
|
||||
}
|
||||
filterWasUsed = true;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -731,8 +736,18 @@ public class DebugProcessEvents extends DebugProcessImpl {
|
||||
// // As resume() implicitly cleares the filter, the filter must be always applied _before_ any resume() action happens
|
||||
// myBreakpointManager.applyThreadFilter(DebugProcessEvents.this, event.thread());
|
||||
//}
|
||||
suspendManager.voteSuspend(suspendContext);
|
||||
showStatusText(DebugProcessEvents.this, event);
|
||||
if (filterWasUsed && suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_EVENT_THREAD &&
|
||||
requestor.needReplaceWithAllThreadSuspendContext()) {
|
||||
// Do not vote to resume.
|
||||
// Instead, create an auxiliary request to correctly stop all threads as soon as possible.
|
||||
// [SuspendOtherThreadsRequestor] will resume this suspendContext when the request hits.
|
||||
// Resume will be without voting.
|
||||
SuspendOtherThreadsRequestor.enableRequest(mySession.getProcess(), suspendContext);
|
||||
}
|
||||
else {
|
||||
suspendManager.voteSuspend(suspendContext);
|
||||
showStatusText(DebugProcessEvents.this, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1853,8 +1853,9 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
|
||||
public RunToCursorCommand(SuspendContextImpl suspendContext, @NotNull XSourcePosition position, final boolean ignoreBreakpoints) {
|
||||
super(suspendContext, null);
|
||||
myIgnoreBreakpoints = ignoreBreakpoints;
|
||||
myRunToCursorBreakpoint =
|
||||
DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager().addRunToCursorBreakpoint(position, ignoreBreakpoints);
|
||||
boolean needReplaceWithAllThreadSuspendContext = suspendContext.getSuspendPolicy() == EventRequest.SUSPEND_ALL;
|
||||
myRunToCursorBreakpoint = DebuggerManagerEx.getInstanceEx(myProject).getBreakpointManager()
|
||||
.addRunToCursorBreakpoint(position, ignoreBreakpoints, needReplaceWithAllThreadSuspendContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -1985,7 +1986,8 @@ public abstract class DebugProcessImpl extends UserDataHolderBase implements Deb
|
||||
}
|
||||
|
||||
protected void applyThreadFilter(@Nullable LightOrRealThreadInfo threadInfo) {
|
||||
if (getSuspendContext().getSuspendPolicy() == EventRequest.SUSPEND_ALL) {
|
||||
boolean isLightThread = threadInfo != null && threadInfo.getRealThread() == null;
|
||||
if (isLightThread || getSuspendContext().getSuspendPolicy() == EventRequest.SUSPEND_ALL) {
|
||||
// there could be explicit resume as a result of call to voteSuspend()
|
||||
// e.g. when breakpoint was considered invalid, in that case the filter will be applied _after_
|
||||
// resuming and all breakpoints in other threads will be ignored.
|
||||
|
||||
@@ -53,6 +53,8 @@ public abstract class SuspendContextImpl extends XSuspendContext implements Susp
|
||||
|
||||
private JavaExecutionStack myActiveExecutionStack;
|
||||
|
||||
private @Nullable ThreadReferenceProxyImpl myAnotherThreadToFocus = null;
|
||||
|
||||
SuspendContextImpl(@NotNull DebugProcessImpl debugProcess,
|
||||
@MagicConstant(flagsFromClass = EventRequest.class) int suspendPolicy,
|
||||
int eventVotes,
|
||||
@@ -334,4 +336,12 @@ public abstract class SuspendContextImpl extends XSuspendContext implements Susp
|
||||
|
||||
private static final Comparator<JavaExecutionStack> THREADS_SUSPEND_AND_NAME_COMPARATOR =
|
||||
Comparator.comparing(JavaExecutionStack::getThreadProxy, SUSPEND_FIRST_COMPARATOR).thenComparing(THREAD_NAME_COMPARATOR);
|
||||
|
||||
public @Nullable ThreadReferenceProxyImpl getAnotherThreadToFocus() {
|
||||
return myAnotherThreadToFocus;
|
||||
}
|
||||
|
||||
public void setAnotherThreadToFocus(@Nullable ThreadReferenceProxyImpl threadToFocus) {
|
||||
myAnotherThreadToFocus = threadToFocus;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
// 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.InstanceFilter;
|
||||
import com.intellij.debugger.engine.events.SuspendContextCommandImpl;
|
||||
import com.intellij.debugger.settings.DebuggerSettings;
|
||||
import com.intellij.debugger.ui.breakpoints.FilteredRequestor;
|
||||
import com.intellij.ui.classFilter.ClassFilter;
|
||||
import com.sun.jdi.event.LocatableEvent;
|
||||
import com.sun.jdi.request.EventRequest;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
class SuspendOtherThreadsRequestor implements FilteredRequestor {
|
||||
|
||||
private static final ClassFilter[] CLASS_EXCLUSION_FILTERS = {
|
||||
new ClassFilter("java.*"),
|
||||
new ClassFilter("jdk.*"),
|
||||
new ClassFilter("sun.*"),
|
||||
};
|
||||
|
||||
private final @NotNull DebugProcessImpl myProcess;
|
||||
private final @NotNull SuspendContextImpl myThreadSuspendContext;
|
||||
|
||||
SuspendOtherThreadsRequestor(@NotNull DebugProcessImpl process, @NotNull SuspendContextImpl threadSuspendContext) {
|
||||
myProcess = process;
|
||||
myThreadSuspendContext = threadSuspendContext;
|
||||
}
|
||||
|
||||
static void enableRequest(DebugProcessImpl process, @NotNull SuspendContextImpl threadSuspendContext) {
|
||||
var requestor = new SuspendOtherThreadsRequestor(process, threadSuspendContext);
|
||||
var request = process.getRequestsManager().createMethodEntryRequest(requestor);
|
||||
|
||||
request.setSuspendPolicy(EventRequest.SUSPEND_ALL);
|
||||
request.setEnabled(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean processLocatableEvent(@NotNull SuspendContextCommandImpl action, LocatableEvent event) {
|
||||
myProcess.getRequestsManager().deleteRequest(this);
|
||||
|
||||
SuspendContextImpl suspendContext = action.getSuspendContext();
|
||||
if (suspendContext == null) return false;
|
||||
|
||||
// Need to 'replace' the myThreadSuspendContext (single-thread suspend context passed filtering) with this one.
|
||||
suspendContext.setAnotherThreadToFocus(myThreadSuspendContext.getThread());
|
||||
|
||||
// Note, myThreadSuspendContext is resuming without SuspendManager#voteSuspend.
|
||||
// Look at the end of DebugProcessEvents#processLocatableEvent for more details.
|
||||
myProcess.getSuspendManager().voteResume(myThreadSuspendContext);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String getSuspendPolicy() {
|
||||
return DebuggerSettings.SUSPEND_ALL;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isInstanceFiltersEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InstanceFilter[] getInstanceFilters() {
|
||||
return InstanceFilter.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCountFilterEnabled() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getCountFilter() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isClassFiltersEnabled() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassFilter[] getClassFilters() {
|
||||
return ClassFilter.EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ClassFilter[] getClassExclusionFilters() {
|
||||
return CLASS_EXCLUSION_FILTERS;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldIgnoreThreadFiltering() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -18,6 +18,14 @@ public interface LocatableEventRequestor extends Requestor {
|
||||
*/
|
||||
String getSuspendPolicy();
|
||||
|
||||
default boolean shouldIgnoreThreadFiltering() {
|
||||
return false;
|
||||
}
|
||||
|
||||
default boolean needReplaceWithAllThreadSuspendContext() {
|
||||
return false;
|
||||
}
|
||||
|
||||
class EventProcessingException extends Exception {
|
||||
private final @NlsContexts.DialogTitle String myTitle;
|
||||
|
||||
|
||||
@@ -368,11 +368,15 @@ public final class DebuggerSession implements AbstractDebuggerSession {
|
||||
public void resume() {
|
||||
final SuspendContextImpl suspendContext = getSuspendContext();
|
||||
if (suspendContext != null) {
|
||||
clearSteppingThrough();
|
||||
resumeAction(myDebugProcess.createResumeCommand(suspendContext), Event.RESUME);
|
||||
resumeSuspendContext(suspendContext);
|
||||
}
|
||||
}
|
||||
|
||||
public void resumeSuspendContext(SuspendContextImpl suspendContext) {
|
||||
clearSteppingThrough();
|
||||
resumeAction(myDebugProcess.createResumeCommand(suspendContext), Event.RESUME);
|
||||
}
|
||||
|
||||
public void resetIgnoreStepFiltersFlag() {
|
||||
myIgnoreFiltersFrameCountThreshold = 0;
|
||||
}
|
||||
@@ -484,7 +488,8 @@ public final class DebuggerSession implements AbstractDebuggerSession {
|
||||
public void paused(final SuspendContextImpl suspendContext) {
|
||||
LOG.debug("paused");
|
||||
|
||||
ThreadReferenceProxyImpl currentThread = suspendContext.getThread();
|
||||
ThreadReferenceProxyImpl anotherThreadToFocus = suspendContext.getAnotherThreadToFocus();
|
||||
ThreadReferenceProxyImpl currentThread = anotherThreadToFocus != null ? anotherThreadToFocus : suspendContext.getThread();
|
||||
|
||||
if (!shouldSetAsActiveContext(suspendContext)) {
|
||||
notifyThreadsRefresh();
|
||||
|
||||
@@ -184,8 +184,8 @@ public class BreakpointManager {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public RunToCursorBreakpoint addRunToCursorBreakpoint(@NotNull XSourcePosition position, final boolean ignoreBreakpoints) {
|
||||
return RunToCursorBreakpoint.create(myProject, position, ignoreBreakpoints);
|
||||
public RunToCursorBreakpoint addRunToCursorBreakpoint(@NotNull XSourcePosition position, boolean ignoreBreakpoints, boolean needReplaceWithAllThreadSuspendContext) {
|
||||
return RunToCursorBreakpoint.create(myProject, position, ignoreBreakpoints, needReplaceWithAllThreadSuspendContext);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
|
||||
@@ -22,11 +22,13 @@ public class RunToCursorBreakpoint extends SyntheticLineBreakpoint implements St
|
||||
private final boolean myRestoreBreakpoints;
|
||||
@NotNull
|
||||
protected final SourcePosition myCustomPosition;
|
||||
private final boolean myNeedReplaceWithAllThreadSuspendContext;
|
||||
|
||||
protected RunToCursorBreakpoint(@NotNull Project project, @NotNull SourcePosition pos, boolean restoreBreakpoints) {
|
||||
protected RunToCursorBreakpoint(@NotNull Project project, @NotNull SourcePosition pos, boolean restoreBreakpoints, boolean needReplaceWithAllThreadSuspendContext) {
|
||||
super(project);
|
||||
myCustomPosition = pos;
|
||||
myRestoreBreakpoints = restoreBreakpoints;
|
||||
myNeedReplaceWithAllThreadSuspendContext = needReplaceWithAllThreadSuspendContext;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
@@ -70,12 +72,15 @@ public class RunToCursorBreakpoint extends SyntheticLineBreakpoint implements St
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected static RunToCursorBreakpoint create(@NotNull Project project, @NotNull XSourcePosition position, boolean restoreBreakpoints) {
|
||||
protected static RunToCursorBreakpoint create(@NotNull Project project,
|
||||
@NotNull XSourcePosition position,
|
||||
boolean restoreBreakpoints,
|
||||
boolean needReplaceWithAllThreadSuspendContext) {
|
||||
PsiFile psiFile = PsiManager.getInstance(project).findFile(position.getFile());
|
||||
if (psiFile == null) {
|
||||
return null;
|
||||
}
|
||||
return new RunToCursorBreakpoint(project, SourcePosition.createFromOffset(psiFile, position.getOffset()), restoreBreakpoints);
|
||||
return new RunToCursorBreakpoint(project, SourcePosition.createFromOffset(psiFile, position.getOffset()), restoreBreakpoints, needReplaceWithAllThreadSuspendContext);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -86,4 +91,9 @@ public class RunToCursorBreakpoint extends SyntheticLineBreakpoint implements St
|
||||
public boolean track() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needReplaceWithAllThreadSuspendContext() {
|
||||
return myNeedReplaceWithAllThreadSuspendContext;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ public class StepIntoBreakpoint extends RunToCursorBreakpoint {
|
||||
@Nullable private RequestHint myHint;
|
||||
|
||||
protected StepIntoBreakpoint(@NotNull Project project, @NotNull SourcePosition pos, @NotNull BreakpointStepMethodFilter filter) {
|
||||
super(project, pos, false);
|
||||
super(project, pos, false, false);
|
||||
myFilter = filter;
|
||||
}
|
||||
|
||||
|
||||
@@ -50,7 +50,6 @@ import com.intellij.testFramework.EdtTestUtil;
|
||||
import com.intellij.testFramework.RunAll;
|
||||
import com.intellij.testFramework.UsefulTestCase;
|
||||
import com.intellij.util.ThrowableRunnable;
|
||||
import com.intellij.util.concurrency.annotations.RequiresBackgroundThread;
|
||||
import com.intellij.util.ui.EDT;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.xdebugger.*;
|
||||
@@ -453,7 +452,20 @@ public abstract class DebuggerTestCase extends ExecutionWithDebuggerToolsTestCas
|
||||
}
|
||||
|
||||
public DebuggerContextImpl createDebuggerContext(final SuspendContextImpl suspendContext) {
|
||||
return createDebuggerContext(suspendContext, suspendContext.getFrameProxy());
|
||||
StackFrameProxyImpl proxy = getFrameProxy(suspendContext);
|
||||
return createDebuggerContext(suspendContext, proxy);
|
||||
}
|
||||
|
||||
protected static StackFrameProxyImpl getFrameProxy(@NotNull SuspendContextImpl suspendContext) {
|
||||
if (suspendContext.getAnotherThreadToFocus() != null) {
|
||||
try {
|
||||
return suspendContext.getAnotherThreadToFocus().frame(0);
|
||||
}
|
||||
catch (EvaluateException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
return suspendContext.getFrameProxy();
|
||||
}
|
||||
|
||||
protected void printLocation(SuspendContextImpl suspendContext) {
|
||||
|
||||
@@ -31,6 +31,8 @@ import org.jetbrains.eval4j.Value
|
||||
import org.jetbrains.eval4j.jdi.asValue
|
||||
import org.jetbrains.kotlin.descriptors.ModuleDescriptor
|
||||
import org.jetbrains.kotlin.idea.KotlinFileType
|
||||
import org.jetbrains.kotlin.idea.base.test.InTextDirectivesUtils.findLinesWithPrefixesRemoved
|
||||
import org.jetbrains.kotlin.idea.base.test.InTextDirectivesUtils.findStringWithPrefixes
|
||||
import org.jetbrains.kotlin.idea.compilerPlugin.kotlinxSerialization.KotlinSerializationEnabledChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.core.CodeFragmentContextTuner
|
||||
import org.jetbrains.kotlin.idea.debugger.evaluate.DebugContextProvider
|
||||
@@ -40,8 +42,6 @@ import org.jetbrains.kotlin.idea.debugger.test.util.FramePrinter
|
||||
import org.jetbrains.kotlin.idea.debugger.test.util.FramePrinterDelegate
|
||||
import org.jetbrains.kotlin.idea.debugger.test.util.KotlinOutputChecker
|
||||
import org.jetbrains.kotlin.idea.debugger.test.util.SteppingInstruction
|
||||
import org.jetbrains.kotlin.idea.base.test.InTextDirectivesUtils.findLinesWithPrefixesRemoved
|
||||
import org.jetbrains.kotlin.idea.base.test.InTextDirectivesUtils.findStringWithPrefixes
|
||||
import org.jetbrains.kotlin.idea.test.KotlinBaseTest
|
||||
import java.io.File
|
||||
import java.util.concurrent.ConcurrentHashMap
|
||||
@@ -210,7 +210,7 @@ abstract class AbstractIrKotlinEvaluateExpressionTest : KotlinDescriptorTestCase
|
||||
val sourcePosition = ContextUtil.getSourcePosition(suspendContext)
|
||||
|
||||
// Default test debuggerContext doesn't provide a valid stackFrame so we have to create one more for evaluation purposes.
|
||||
val frameProxy = suspendContext.frameProxy
|
||||
val frameProxy = getFrameProxy(suspendContext)
|
||||
val threadProxy = frameProxy?.threadProxy()
|
||||
val debuggerContext = createDebuggerContext(myDebuggerSession, suspendContext, threadProxy, frameProxy)
|
||||
debuggerContext.initCaches()
|
||||
|
||||
@@ -91,7 +91,7 @@ abstract class KotlinDescriptorTestCaseWithStepping : KotlinDescriptorTestCase()
|
||||
|
||||
private fun SuspendContextImpl.getKotlinStackFrames(): List<KotlinStackFrame> {
|
||||
if (myInProgress) {
|
||||
val proxy = frameProxy ?: return emptyList()
|
||||
val proxy = getFrameProxy(this) ?: return emptyList()
|
||||
return KotlinPositionManager(debugProcess)
|
||||
.createStackFrames(StackFrameDescriptorImpl(proxy, MethodsTracker()))
|
||||
.filterIsInstance<KotlinStackFrame>()
|
||||
@@ -251,7 +251,10 @@ abstract class KotlinDescriptorTestCaseWithStepping : KotlinDescriptorTestCase()
|
||||
return@runReadAction println("Context thread is null", ProcessOutputTypes.SYSTEM)
|
||||
}
|
||||
|
||||
val sourcePosition = PositionUtil.getSourcePosition(this)
|
||||
val sourcePosition = if (anotherThreadToFocus != null)
|
||||
debugProcess.positionManager.getSourcePosition(getFrameProxy(this).location())
|
||||
else
|
||||
PositionUtil.getSourcePosition(this)
|
||||
println(sourcePosition?.render() ?: "null", ProcessOutputTypes.SYSTEM)
|
||||
extraPrintContext(this)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user