Skip to content

Commit

Permalink
Fix multithreading stepping in 3.12 and later (#1798)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
rchiodo authored Jan 7, 2025
1 parent 7597262 commit 02723de
Show file tree
Hide file tree
Showing 8 changed files with 1,960 additions and 1,853 deletions.
4 changes: 4 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
7 changes: 7 additions & 0 deletions src/debugpy/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"python.testing.pytestArgs": [
"."
],
"python.testing.unittestEnabled": false,
"python.testing.pytestEnabled": true
}
Original file line number Diff line number Diff line change
Expand Up @@ -1084,7 +1084,12 @@ def _return_event(code, instruction, retval):
if func_code_info.plugin_return_stepping:
_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):
Expand Down
3,773 changes: 1,922 additions & 1,851 deletions src/debugpy/_vendored/pydevd/_pydevd_sys_monitoring/_pydevd_sys_monitoring_cython.c

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -1090,7 +1090,12 @@ cdef _return_event(code, instruction, retval):
if func_code_info.plugin_return_stepping:
_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):
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,18 @@
"""

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
Expand All @@ -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

Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 2 additions & 0 deletions tests/debug/session.py
Original file line number Diff line number Diff line change
Expand Up @@ -867,6 +867,8 @@ def wait_for_stop(
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)

Expand Down

0 comments on commit 02723de

Please sign in to comment.