mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
PY-65353 Make sure debugger stops on breakpoints in every subprocess
GitOrigin-RevId: 908ef552d42f569471410a8c24bcc05a9e571946
This commit is contained in:
committed by
intellij-monorepo-bot
parent
3c81687759
commit
6ee09adb6e
@@ -594,73 +594,76 @@ public class RemoteDebugger implements ProcessDebugger {
|
||||
|
||||
// todo: extract response processing
|
||||
private void processThreadEvent(ProtocolFrame frame) throws PyDebuggerException {
|
||||
switch (frame.getCommand()) {
|
||||
case AbstractCommand.CREATE_THREAD -> {
|
||||
final PyThreadInfo thread = parseThreadEvent(frame);
|
||||
if (!thread.isPydevThread()) { // ignore pydevd threads
|
||||
myThreads.put(thread.getId(), thread);
|
||||
if (myDebugProcess.getSession().isSuspended() && myDebugProcess.isSuspendedOnAllThreadsPolicy()) {
|
||||
// Sometimes the notification about new threads may come slow from the Python side. We should check if
|
||||
// the current session is suspended in the "Suspend all threads" mode and suspend new thread, which hasn't been suspended
|
||||
suspendThread(thread.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
case AbstractCommand.SUSPEND_THREAD -> {
|
||||
final PyThreadInfo event = parseThreadEvent(frame);
|
||||
PyThreadInfo thread = myThreads.get(event.getId());
|
||||
if (thread == null) {
|
||||
LOG.error("Trying to stop on non-existent thread: " + event.getId() + ", " + event.getStopReason() + ", " + event.getMessage());
|
||||
myThreads.put(event.getId(), event);
|
||||
thread = event;
|
||||
}
|
||||
thread.updateState(PyThreadInfo.State.SUSPENDED, event.getFrames());
|
||||
thread.setStopReason(event.getStopReason());
|
||||
thread.setMessage(event.getMessage());
|
||||
boolean updateSourcePosition = true;
|
||||
if (event.getStopReason() == AbstractCommand.SUSPEND_THREAD) {
|
||||
// That means that the thread was stopped manually from the Java side either while suspending all threads
|
||||
// or after the "Pause" command. In both cases we shouldn't change debugger focus if session is already suspended.
|
||||
updateSourcePosition = !myDebugProcess.getSession().isSuspended();
|
||||
}
|
||||
myDebugProcess.threadSuspended(thread, updateSourcePosition);
|
||||
}
|
||||
case AbstractCommand.RESUME_THREAD -> {
|
||||
final String id = ProtocolParser.getThreadId(frame.getPayload());
|
||||
final PyThreadInfo thread = myThreads.get(id);
|
||||
if (thread != null) {
|
||||
thread.updateState(PyThreadInfo.State.RUNNING, null);
|
||||
myDebugProcess.threadResumed(thread);
|
||||
}
|
||||
}
|
||||
case AbstractCommand.KILL_THREAD -> {
|
||||
final String id = frame.getPayload();
|
||||
final PyThreadInfo thread = myThreads.get(id);
|
||||
if (thread != null) {
|
||||
thread.updateState(PyThreadInfo.State.KILLED, null);
|
||||
myThreads.remove(id);
|
||||
}
|
||||
if (myDebugProcess.getSession().getCurrentPosition() == null) {
|
||||
for (PyThreadInfo threadInfo : myThreads.values()) {
|
||||
// notify UI of suspended threads left in debugger if one thread finished its work
|
||||
if ((threadInfo != null) && (threadInfo.getState() == PyThreadInfo.State.SUSPENDED)) {
|
||||
myDebugProcess.threadResumed(threadInfo);
|
||||
myDebugProcess.threadSuspended(threadInfo, true);
|
||||
// The method must be synchronized because in the case of multiprocess debugging,
|
||||
// each process `RemoteDebugger` shares the same session. Altering the session's state
|
||||
// in an unsynchronized manner can cause race conditions.
|
||||
synchronized (myDebugProcess.getSession()) {
|
||||
switch (frame.getCommand()) {
|
||||
case AbstractCommand.CREATE_THREAD -> {
|
||||
final PyThreadInfo thread = parseThreadEvent(frame);
|
||||
if (!thread.isPydevThread()) { // ignore pydevd threads
|
||||
myThreads.put(thread.getId(), thread);
|
||||
if (myDebugProcess.getSession().isSuspended() && myDebugProcess.isSuspendedOnAllThreadsPolicy()) {
|
||||
// Sometimes the notification about new threads may come slow from the Python side. We should check if
|
||||
// the current session is suspended in the "Suspend all threads" mode and suspend new thread, which hasn't been suspended
|
||||
suspendThread(thread.getId());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case AbstractCommand.SHOW_CONSOLE -> {
|
||||
final PyThreadInfo event = parseThreadEvent(frame);
|
||||
PyThreadInfo thread = myThreads.get(event.getId());
|
||||
if (thread == null) {
|
||||
myThreads.put(event.getId(), event);
|
||||
thread = event;
|
||||
case AbstractCommand.SUSPEND_THREAD -> {
|
||||
final PyThreadInfo event = parseThreadEvent(frame);
|
||||
PyThreadInfo thread = myThreads.get(event.getId());
|
||||
if (thread == null) {
|
||||
LOG.error("Trying to stop on non-existent thread: " + event.getId() + ", " + event.getStopReason() + ", " + event.getMessage());
|
||||
myThreads.put(event.getId(), event);
|
||||
thread = event;
|
||||
}
|
||||
thread.updateState(PyThreadInfo.State.SUSPENDED, event.getFrames());
|
||||
thread.setStopReason(event.getStopReason());
|
||||
thread.setMessage(event.getMessage());
|
||||
boolean updateSourcePosition = true;
|
||||
if (event.getStopReason() == AbstractCommand.SUSPEND_THREAD || event.getStopReason() == AbstractCommand.SET_BREAKPOINT) {
|
||||
updateSourcePosition = !myDebugProcess.getSession().isSuspended();
|
||||
}
|
||||
myDebugProcess.threadSuspended(thread, updateSourcePosition);
|
||||
}
|
||||
case AbstractCommand.RESUME_THREAD -> {
|
||||
final String id = ProtocolParser.getThreadId(frame.getPayload());
|
||||
final PyThreadInfo thread = myThreads.get(id);
|
||||
if (thread != null) {
|
||||
thread.updateState(PyThreadInfo.State.RUNNING, null);
|
||||
myDebugProcess.threadResumed(thread);
|
||||
}
|
||||
}
|
||||
case AbstractCommand.KILL_THREAD -> {
|
||||
final String id = frame.getPayload();
|
||||
final PyThreadInfo thread = myThreads.get(id);
|
||||
if (thread != null) {
|
||||
thread.updateState(PyThreadInfo.State.KILLED, null);
|
||||
myThreads.remove(id);
|
||||
}
|
||||
if (myDebugProcess.getSession().getCurrentPosition() == null) {
|
||||
for (PyThreadInfo threadInfo : myThreads.values()) {
|
||||
// notify UI of suspended threads left in debugger if one thread finished its work
|
||||
if ((threadInfo != null) && (threadInfo.getState() == PyThreadInfo.State.SUSPENDED)) {
|
||||
myDebugProcess.threadResumed(threadInfo);
|
||||
myDebugProcess.threadSuspended(threadInfo, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
case AbstractCommand.SHOW_CONSOLE -> {
|
||||
final PyThreadInfo event = parseThreadEvent(frame);
|
||||
PyThreadInfo thread = myThreads.get(event.getId());
|
||||
if (thread == null) {
|
||||
myThreads.put(event.getId(), event);
|
||||
thread = event;
|
||||
}
|
||||
thread.updateState(PyThreadInfo.State.SUSPENDED, event.getFrames());
|
||||
thread.setStopReason(event.getStopReason());
|
||||
thread.setMessage(event.getMessage());
|
||||
myDebugProcess.showConsole(thread);
|
||||
}
|
||||
thread.updateState(PyThreadInfo.State.SUSPENDED, event.getFrames());
|
||||
thread.setStopReason(event.getStopReason());
|
||||
thread.setMessage(event.getMessage());
|
||||
myDebugProcess.showConsole(thread);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,6 +107,14 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
|
||||
new ConcurrentHashMap<>();
|
||||
|
||||
private final Set<PyThreadInfo> mySuspendedThreads = Collections.synchronizedSet(new HashSet<>());
|
||||
|
||||
private record BreakpointHitContext(@NotNull XBreakpoint<?> breakpoint,
|
||||
@Nullable String evaluatedLogExpression,
|
||||
@NotNull XSuspendContext suspendContext) {
|
||||
}
|
||||
|
||||
private final List<BreakpointHitContext> myBreakpointHits = new LinkedList<>();
|
||||
|
||||
private final Map<String, XValueChildrenList> myStackFrameCache = Maps.newConcurrentMap();
|
||||
private final Object myFrameCacheObject = new Object();
|
||||
private final Map<String, PyDebugValue> myNewVariableValue = Maps.newHashMap();
|
||||
@@ -649,7 +657,17 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
|
||||
|
||||
@Override
|
||||
public void resume(@Nullable XSuspendContext context) {
|
||||
passResumeToAllThreads();
|
||||
if (myBreakpointHits.isEmpty()) {
|
||||
passResumeToAllThreads();
|
||||
}
|
||||
else {
|
||||
var breakpointHitContext = myBreakpointHits.remove(0);
|
||||
var shouldStop = getSession().breakpointReached(breakpointHitContext.breakpoint, breakpointHitContext.evaluatedLogExpression,
|
||||
breakpointHitContext.suspendContext);
|
||||
if (!shouldStop) {
|
||||
resume(breakpointHitContext.suspendContext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -708,6 +726,7 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
|
||||
|
||||
private void passToCurrentThread(@Nullable XSuspendContext context, final ResumeOrStepCommand.Mode mode) {
|
||||
dropFrameCaches();
|
||||
myBreakpointHits.clear();
|
||||
if (isConnected()) {
|
||||
String threadId = threadIdBeforeResumeOrStep(context);
|
||||
|
||||
@@ -801,7 +820,8 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
|
||||
}
|
||||
|
||||
@Override
|
||||
public String execTableCommand(String command, TableCommandType commandType, TableCommandParameters tableCommandParameters) throws PyDebuggerException {
|
||||
public String execTableCommand(String command, TableCommandType commandType, TableCommandParameters tableCommandParameters)
|
||||
throws PyDebuggerException {
|
||||
final PyStackFrame frame = currentFrame();
|
||||
return myDebugger.execTableCommand(frame.getThreadId(), frame.getFrameId(), command, commandType, tableCommandParameters);
|
||||
}
|
||||
@@ -836,9 +856,9 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public XValueChildrenList loadSpecialVariables(ProcessDebugger.GROUP_TYPE groupType) throws PyDebuggerException {
|
||||
public XValueChildrenList loadSpecialVariables(ProcessDebugger.GROUP_TYPE groupType) throws PyDebuggerException {
|
||||
final PyStackFrame frame = currentFrame();
|
||||
XValueChildrenList values = myDebugger.loadFrame(frame.getThreadId(), frame.getFrameId(), groupType);
|
||||
XValueChildrenList values = myDebugger.loadFrame(frame.getThreadId(), frame.getFrameId(), groupType);
|
||||
if (values != null) {
|
||||
PyDebugValue.getAsyncValues(frame, this, values);
|
||||
}
|
||||
@@ -1176,13 +1196,20 @@ public class PyDebugProcess extends XDebugProcess implements IPyDebugProcess, Pr
|
||||
|
||||
if (updateSourcePosition) {
|
||||
if (breakpoint != null) {
|
||||
boolean shouldSuspend = getSession().breakpointReached(breakpoint, threadInfo.getMessage(), suspendContext);
|
||||
if (!shouldSuspend) resume(suspendContext);
|
||||
if (!getSession().breakpointReached(breakpoint, threadInfo.getMessage(), suspendContext)) {
|
||||
resume(suspendContext);
|
||||
}
|
||||
}
|
||||
else {
|
||||
((XDebugSessionImpl)getSession()).positionReached(suspendContext, isFailedTestStop(threadInfo));
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (breakpoint != null) {
|
||||
// Hit a breakpoint while already suspended. We have to remember it and stop on this breakpoint later.
|
||||
myBreakpointHits.add(new BreakpointHitContext(breakpoint, threadInfo.getMessage(), suspendContext));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user