-
Notifications
You must be signed in to change notification settings - Fork 191
Description
Environment data
- debugpy version: 1.8.19 (from ms-python.debugpy-2025.18.0-win32-x64 extension)
- OS and version: Windows 10 19045.6456
- Python version (& distribution if applicable, e.g. Anaconda): Tested on Python 3.8.10, 3.12.10 and 3.14.0
- Using VS Code or Visual Studio: VS Code 1.111
Actual behavior
When attaching to a debugpy instance with an in-process adapter, if there are multiple threads then only one thread gets resumed after pressing 'continue' instead of all of them, and it desyncs from the visual state in VS code as it thinks all threads are running.
Expected behavior
Attaching to a debugpy instance with an in-process adapter should behave the same as with a separate adapter process, if there is only one process being debugged.
For my real use case I'm trying to debug python embedded in an application, so using the out-of-process adapter would be difficult and much less convenient.
Steps to reproduce:
- Create
repro.pywhich (if not already loaded) will load debugpy with an in-process adapter and run two threads to print in a loop:
import threading, time, sys
if 'debugpy' not in sys.modules.keys():
import debugpy
debugpy.configure(subProcess=False)
debugpy.listen(5678, in_process_debug_adapter=True)
def f():
for i in range(100):
print(f"Thread {i}")
time.sleep(0.3)
threading.Thread(target=f).start()
for i in range(100):
print(f"Main {i}")
time.sleep(0.3)- Create a launch.json with 'Python Debugger: Remote Attach' to the default host+port (omitted for brevity, I remove the default pathMappings)
- Set PYTHONPATH so that debugpy can be found (for me e.g.
C:\Users\baldurk\.vscode\extensions\ms-python.debugpy-2025.18.0-win32-x64\bundled\libs) - Run first via the debugpy module (this creates an out-of-process adapter):
$ python -m debugpy --listen localhost:5678 repro.py - Attach from VS code, breakpoint on one of the
printlines to stop debugging. VS Code shows both threads paused as expected. - Remove the breakpoint and continue. Both threads are shown as running now in VS Code and both continue printing.
- Now run directly to let the script open an in-process adapter:
$ python repro.py - Attach from VS code and repeat the steps to breakpoint and continue. Only the thread that hit the breakpoint continues printing, the other remains paused even though both threads are shown as running in VS Code.
Logs
From running with DEBUGPY_LOG_DIR, here are some verbose logs from running with python 3.14:
Working case, out of process adapter:
debugpy.server-20952.log
debugpy.pydevd.20952.log
debugpy.adapter-30776.log
Broken case, in-process adapter:
debugpy.server-27444.log
debugpy.pydevd.27444.log
Thoughts/findings
I've separated out what I think is happening, since I'm not much of a python programmer, I'm unfamiliar with these codebases and the DAP. This may be misguided nonsense, please ignore if so 😄.
The key difference seems to be this (working):
0.83s - Process ContinueRequest: {
"arguments": {
"threadId": "*"
},
"command": "continue",
"seq": 18,
"type": "request"
}
vs this (broken):
2.17s - Process ContinueRequest: {
"arguments": {
"threadId": 1
},
"command": "continue",
"seq": 15,
"type": "request"
}
Tracing it through, VS Code always sends a threadId=X for the active thread with its ContinueRequest. The debugpy adapter process receives this, and forwards on with threadId forcibly set to * in Client.continue_request which causes pydevd to resume all threads.
When there's no separate adapter process, the threadId goes directly(?) to pydevd which then resumes only that one single thread. pydevd returns two responses - one from the requested single-notification mode which says allThreadsContinued=True and that arrives before a second from on_continue_request which correctly has allThreadsContinued=False:
0.00s - sending cmd (http_json) --> CMD_THREAD_RESUME_SINGLE_NOTIFICATION {"type": "event", "event": "continued", "body": {"threadId": 1, "allThreadsContinued": true}, "seq": 84, "pydevd_cmd_id": 158}
0.00s - sending cmd (http_json) --> CMD_RETURN {"type": "response", "request_seq": 15, "success": true, "command": "continue", "body": {"allThreadsContinued": false}, "seq": 86, "pydevd_cmd_id": 502}
I tried hacking around this. Forcing pydev to resume all threads unsurprisingly behaves correctly. Hacking the return data from make_thread_resume_single_notification to set allThreadsContinued=False makes VS code behave a bit better - it realises only one thread has resumed, but the behaviour after that point becomes inconsistent and weird. From reading elsewhere, I'm not sure VS expects to be pausing threads independently.
I'm not sure what the fix should be. From the docs in pydevd_schema.py it sounds like the threadId in ContinueArguments should only actually be used if singleThread is true, which it is not. The capability supportsSingleThreadExecutionRequests also seems to be false I think.
So maybe pydevd is incorrect in resuming only one thread when the threadId is passed and it should always behave like threadId=* unless single-thread-resuming is both supported and explicitly enabled in the continue request?