Fix multithreading stepping in 3.12 and later (#1798)

* Fix multithreaded stepping to not have 'return' events when a thread is already suspended

* Update after removing blank line

* Remove unnecessary change for start method
This commit is contained in:
Rich Chiodo 2025-01-07 11:25:54 -08:00 committed by GitHub
parent 7597262c80
commit 02723de140
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 1962 additions and 1855 deletions

View file

@ -171,6 +171,10 @@ That will generate a log from the test run.
Logging the test output can be tricky so here's some information on how to debug the tests.
#### Running pydevd tests inside of VS code
You can also run the pydevd tests inside of VS code using the test explorer (and debug the pytest code). To do so, set PYTHONPATH=. and open the `src/debugpy/_vendored/pydevd` folder in VS code. The test explorer should find all of the pydevd tests.
#### How to add more logging
The pydevd tests log everything to the console and to a text file during the test. If you scroll up in the console, it should show the log file it read the logs from:

7
src/debugpy/.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"."
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}

View file

@ -1085,6 +1085,11 @@ def _return_event(code, instruction, retval):
_plugin_stepping(py_db, step_cmd, "return", frame, thread_info)
return
if info.pydev_state == STATE_SUSPEND:
# We're already suspended, don't handle any more events on this thread.
_do_wait_suspend(py_db, thread_info, frame, "return", None)
return
# Python line stepping
stop_frame = info.pydev_step_stop
if step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE):

View file

@ -1091,6 +1091,11 @@ cdef _return_event(code, instruction, retval):
_plugin_stepping(py_db, step_cmd, "return", frame, thread_info)
return
if info.pydev_state == STATE_SUSPEND:
# We're already suspended, don't handle any more events on this thread.
_do_wait_suspend(py_db, thread_info, frame, "return", None)
return
# Python line stepping
stop_frame = info.pydev_step_stop
if step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE):

View file

@ -5,12 +5,18 @@ or should keep waiting for the thread 2 to run if only thread 1 is resumed.
"""
import threading
import requests
import time
event0 = threading.Event()
event1 = threading.Event()
event2 = threading.Event()
event3 = threading.Event()
def request_get(url):
# return "abc"
with requests.get(url) as data:
return data.text
def _thread1():
_event1_set = False
@ -19,6 +25,7 @@ def _thread1():
while not event0.is_set():
event0.wait(timeout=0.001)
time.sleep(.1)
event1.set() # Break thread 1
_event1_set = True
@ -33,6 +40,9 @@ def _thread2():
event0.set()
while not event1.is_set():
# Do something interesting that takes a while. This verifies we
# only get stop events for the thread with a breakpoint.
print(len(request_get("https://dns.google//")))
event1.wait(timeout=0.001)
event2.set()

View file

@ -3359,6 +3359,9 @@ def test_step_next_step_in_multi_threads(case_setup_dap, stepping_resumes_all_th
thread_name_to_id = dict((t["name"], t["id"]) for t in response.body.threads)
assert json_hit.thread_id == thread_name_to_id["thread1"]
stopped_events = json_facade.mark_messages(StoppedEvent)
assert len(stopped_events) == 1
timeout_at = time.time() + 30
checks = 0

View file

@ -867,6 +867,8 @@ class Session(object):
assert len(expected_frames) <= len(frames)
assert expected_frames == frames[0 : len(expected_frames)]
assert len(frames) > 0
fid = frames[0]("id", int)
return StopInfo(stopped, frames, tid, fid)