Skip to content

Commit 02723de

Browse files
authored
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
1 parent 7597262 commit 02723de

8 files changed

Lines changed: 1960 additions & 1853 deletions

File tree

CONTRIBUTING.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -171,6 +171,10 @@ That will generate a log from the test run.
171171

172172
Logging the test output can be tricky so here's some information on how to debug the tests.
173173

174+
#### Running pydevd tests inside of VS code
175+
176+
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.
177+
174178
#### How to add more logging
175179

176180
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:

src/debugpy/.vscode/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{
2+
"python.testing.pytestArgs": [
3+
"."
4+
],
5+
"python.testing.unittestEnabled": false,
6+
"python.testing.pytestEnabled": true
7+
}

src/debugpy/_vendored/pydevd/_pydevd_sys_monitoring/_pydevd_sys_monitoring.py

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1084,7 +1084,12 @@ def _return_event(code, instruction, retval):
10841084
if func_code_info.plugin_return_stepping:
10851085
_plugin_stepping(py_db, step_cmd, "return", frame, thread_info)
10861086
return
1087-
1087+
1088+
if info.pydev_state == STATE_SUSPEND:
1089+
# We're already suspended, don't handle any more events on this thread.
1090+
_do_wait_suspend(py_db, thread_info, frame, "return", None)
1091+
return
1092+
10881093
# Python line stepping
10891094
stop_frame = info.pydev_step_stop
10901095
if step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE):

src/debugpy/_vendored/pydevd/_pydevd_sys_monitoring/_pydevd_sys_monitoring_cython.c

Lines changed: 1922 additions & 1851 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/debugpy/_vendored/pydevd/_pydevd_sys_monitoring/_pydevd_sys_monitoring_cython.pyx

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1090,7 +1090,12 @@ cdef _return_event(code, instruction, retval):
10901090
if func_code_info.plugin_return_stepping:
10911091
_plugin_stepping(py_db, step_cmd, "return", frame, thread_info)
10921092
return
1093-
1093+
1094+
if info.pydev_state == STATE_SUSPEND:
1095+
# We're already suspended, don't handle any more events on this thread.
1096+
_do_wait_suspend(py_db, thread_info, frame, "return", None)
1097+
return
1098+
10941099
# Python line stepping
10951100
stop_frame = info.pydev_step_stop
10961101
if step_cmd in (CMD_STEP_INTO, CMD_STEP_INTO_MY_CODE, CMD_STEP_INTO_COROUTINE):

src/debugpy/_vendored/pydevd/tests_python/resources/_debugger_case_multi_threads_stepping.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,18 @@
55
"""
66

77
import threading
8+
import requests
9+
import time
810

911
event0 = threading.Event()
1012
event1 = threading.Event()
1113
event2 = threading.Event()
1214
event3 = threading.Event()
1315

16+
def request_get(url):
17+
# return "abc"
18+
with requests.get(url) as data:
19+
return data.text
1420

1521
def _thread1():
1622
_event1_set = False
@@ -19,6 +25,7 @@ def _thread1():
1925
while not event0.is_set():
2026
event0.wait(timeout=0.001)
2127

28+
time.sleep(.1)
2229
event1.set() # Break thread 1
2330
_event1_set = True
2431

@@ -33,6 +40,9 @@ def _thread2():
3340
event0.set()
3441

3542
while not event1.is_set():
43+
# Do something interesting that takes a while. This verifies we
44+
# only get stop events for the thread with a breakpoint.
45+
print(len(request_get("https://dns.google//")))
3646
event1.wait(timeout=0.001)
3747

3848
event2.set()

src/debugpy/_vendored/pydevd/tests_python/test_debugger_json.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3359,6 +3359,9 @@ def test_step_next_step_in_multi_threads(case_setup_dap, stepping_resumes_all_th
33593359
thread_name_to_id = dict((t["name"], t["id"]) for t in response.body.threads)
33603360
assert json_hit.thread_id == thread_name_to_id["thread1"]
33613361

3362+
stopped_events = json_facade.mark_messages(StoppedEvent)
3363+
assert len(stopped_events) == 1
3364+
33623365
timeout_at = time.time() + 30
33633366
checks = 0
33643367

tests/debug/session.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -867,6 +867,8 @@ def wait_for_stop(
867867
assert len(expected_frames) <= len(frames)
868868
assert expected_frames == frames[0 : len(expected_frames)]
869869

870+
assert len(frames) > 0
871+
870872
fid = frames[0]("id", int)
871873
return StopInfo(stopped, frames, tid, fid)
872874

0 commit comments

Comments
 (0)