Fix debugger stepping actions in forked process (#1921)

* Fix debugger stepping actions in forked process

Fix the debugger stepping state when debugging a process that has been
forked from the main process. The new sys.monitoring mechanism didn't
fully clear the thread local storage after a fork leading to a state
where the forked child process tracked the wrong thread information and
was never updated on the latest continue action.

* Add stepping test for forked process

* Add line ending back in for cleaner diff

* More formatting reversions
This commit is contained in:
Jordan Borean 2025-07-09 03:47:47 +10:00 committed by GitHub
parent b387710b7f
commit ea1dd9a838
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 2424 additions and 2231 deletions

View file

@ -115,7 +115,7 @@ In order to update the source, you would:
You might need to regenerate the Cython modules after any changes. This can be done by:
- Install Python latest (3.12 as of this writing)
- pip install cython, django>=1.9, setuptools>=0.9, wheel>0.21, twine
- pip install cython 'django>=1.9' 'setuptools>=0.9' 'wheel>0.21' twine
- On a windows machine:
- set FORCE_PYDEVD_VC_VARS=C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools\VC\Auxiliary\Build\vcvars64.bat
- in the pydevd folder: python .\build_tools\build.py

View file

@ -755,6 +755,16 @@ def enable_code_tracing(thread_ident: Optional[int], code, frame) -> bool:
return _enable_code_tracing(py_db, additional_info, func_code_info, code, frame, False)
# fmt: off
# IFDEF CYTHON
# cpdef reset_thread_local_info():
# ELSE
def reset_thread_local_info():
# ENDIF
# fmt: on
"""Resets the thread local info TLS store for use after a fork()."""
global _thread_local_info
_thread_local_info = threading.local()
# fmt: off
# IFDEF CYTHON

View file

@ -761,6 +761,16 @@ cpdef enable_code_tracing(unsigned long thread_ident, code, frame):
return _enable_code_tracing(py_db, additional_info, func_code_info, code, frame, False)
# fmt: off
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)
cpdef reset_thread_local_info():
# ELSE
# def reset_thread_local_info():
# ENDIF
# fmt: on
"""Resets the thread local info TLS store for use after a fork()."""
global _thread_local_info
_thread_local_info = threading.local()
# fmt: off
# IFDEF CYTHON -- DONT EDIT THIS FILE (it is automatically generated)

View file

@ -3354,6 +3354,9 @@ def settrace_forked(setup_tracing=True):
if clear_thread_local_info is not None:
clear_thread_local_info()
if PYDEVD_USE_SYS_MONITORING:
pydevd_sys_monitoring.reset_thread_local_info()
settrace(
host,
port=port,

View file

@ -87,7 +87,7 @@ def test_multiprocessing(pyfile, target, run, start_method):
p.join()
def child(q, a):
print("entering child")
print("entering child") # @bp
assert q.get() == "foo?"
a.put(Foo())
@ -136,7 +136,20 @@ def test_multiprocessing(pyfile, target, run, start_method):
with debug.Session(child_config) as child_session:
with child_session.start():
pass
child_session.set_breakpoints(code_to_debug, all)
expected_frame = some.dap.frame(code_to_debug, line="bp")
stop = child_session.wait_for_stop(
"breakpoint",
expected_frames=[expected_frame],
)
child_session.request('stepIn', {"threadId": stop.thread_id})
stop = child_session.wait_for_stop(
"step",
expected_frames=[some.dap.frame(code_to_debug, line=expected_frame.items['line'] + 1)],
)
child_session.request_continue()
expected_grandchild_config = expected_subprocess_config(child_session)
grandchild_config = child_session.wait_for_next_event("debugpyAttach")