https://github.com/python/cpython/commit/f025dba62e4deee9cb740cb94dcdf0a9b0a229cc
commit: f025dba62e4deee9cb740cb94dcdf0a9b0a229cc
branch: main
author: Maurycy Pawłowski-Wieroński <[email protected]>
committer: pablogsal <[email protected]>
date: 2026-05-05T01:33:56+01:00
summary:
gh-149230: `_remote_debugging`: Fix async-aware for tasks in non-main threads
(#149235)
files:
M Lib/test/test_external_inspection.py
M Modules/_remote_debugging/threads.c
diff --git a/Lib/test/test_external_inspection.py
b/Lib/test/test_external_inspection.py
index 401136de8de666..a29e6cdbbf6c78 100644
--- a/Lib/test/test_external_inspection.py
+++ b/Lib/test/test_external_inspection.py
@@ -1437,6 +1437,160 @@ def matches_awaited_by_pattern(task):
finally:
_cleanup_sockets(client_socket, server_socket)
+ @skip_if_not_supported
+ @unittest.skipIf(
+ sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
+ "Test only runs on Linux with process_vm_readv support",
+ )
+ def test_async_global_awaited_by_from_non_main_thread(self):
+ port = find_unused_port()
+ script = textwrap.dedent(
+ f"""\
+ import asyncio
+ import socket
+ import threading
+ import time
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.connect(('localhost', {port}))
+
+ async def worker_main():
+ task = asyncio.create_task(
+ asyncio.sleep(10_000),
+ name="worker task",
+ )
+ await asyncio.sleep(0)
+
sock.sendall(f"ready:{{threading.get_native_id()}}\\n".encode())
+ await task
+
+ def run_worker_loop():
+ asyncio.run(worker_main())
+
+ threading.Thread(
+ target=run_worker_loop,
+ name="async-worker",
+ daemon=True,
+ ).start()
+ time.sleep(10_000)
+ """
+ )
+
+ with os_helper.temp_dir() as work_dir:
+ script_dir = os.path.join(work_dir, "script_pkg")
+ os.mkdir(script_dir)
+
+ server_socket = _create_server_socket(port)
+ script_name = _make_test_script(script_dir, "script", script)
+ client_socket = None
+
+ try:
+ with _managed_subprocess([sys.executable, script_name]) as p:
+ client_socket, _ = server_socket.accept()
+ server_socket.close()
+ server_socket = None
+
+ response = _wait_for_signal(client_socket, b"ready:")
+ worker_thread_id = int(
+ response.split(b"ready:", 1)[1].splitlines()[0]
+ )
+
+ for _ in busy_retry(SHORT_TIMEOUT):
+ all_awaited_by = get_all_awaited_by(p.pid)
+ if any(
+ task.task_name == "worker task"
+ for info in all_awaited_by
+ if info.thread_id == worker_thread_id
+ for task in info.awaited_by
+ ):
+ break
+ else:
+ self.fail(
+ "get_all_awaited_by() did not report "
+ "the asyncio task from the non-main thread"
+ )
+ finally:
+ _cleanup_sockets(client_socket, server_socket)
+
+ @skip_if_not_supported
+ @unittest.skipIf(
+ sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
+ "Test only runs on Linux with process_vm_readv support",
+ )
+ def test_async_remote_stack_trace_from_non_main_thread(self):
+ port = find_unused_port()
+ script = textwrap.dedent(
+ f"""\
+ import asyncio
+ import socket
+ import threading
+ import time
+
+ sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ sock.connect(('localhost', {port}))
+
+ def blocking_call():
+
sock.sendall(f"ready:{{threading.get_native_id()}}\\n".encode())
+ time.sleep(10_000)
+
+ async def worker_task():
+ await asyncio.sleep(0)
+ blocking_call()
+
+ async def worker_main():
+ task = asyncio.create_task(
+ worker_task(),
+ name="worker task",
+ )
+ await task
+
+ def run_worker_loop():
+ asyncio.run(worker_main())
+
+ threading.Thread(
+ target=run_worker_loop,
+ name="async-worker",
+ daemon=True,
+ ).start()
+ time.sleep(10_000)
+ """
+ )
+
+ with os_helper.temp_dir() as work_dir:
+ script_dir = os.path.join(work_dir, "script_pkg")
+ os.mkdir(script_dir)
+
+ server_socket = _create_server_socket(port)
+ script_name = _make_test_script(script_dir, "script", script)
+ client_socket = None
+
+ try:
+ with _managed_subprocess([sys.executable, script_name]) as p:
+ client_socket, _ = server_socket.accept()
+ server_socket.close()
+ server_socket = None
+
+ response = _wait_for_signal(client_socket, b"ready:")
+ worker_thread_id = int(
+ response.split(b"ready:", 1)[1].splitlines()[0]
+ )
+
+ for _ in busy_retry(SHORT_TIMEOUT):
+ stack_trace = get_async_stack_trace(p.pid)
+ if any(
+ task.task_name == "worker task"
+ for info in stack_trace
+ if info.thread_id == worker_thread_id
+ for task in info.awaited_by
+ ):
+ break
+ else:
+ self.fail(
+ "get_async_stack_trace() did not report "
+ "the running asyncio task from the non-main thread"
+ )
+ finally:
+ _cleanup_sockets(client_socket, server_socket)
+
@skip_if_not_supported
@unittest.skipIf(
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
diff --git a/Modules/_remote_debugging/threads.c
b/Modules/_remote_debugging/threads.c
index e303c667ea013a..d775234b8d78d7 100644
--- a/Modules/_remote_debugging/threads.c
+++ b/Modules/_remote_debugging/threads.c
@@ -34,11 +34,11 @@ iterate_threads(
if (0 > _Py_RemoteDebug_PagedReadRemoteMemory(
&unwinder->handle,
- unwinder->interpreter_addr +
(uintptr_t)unwinder->debug_offsets.interpreter_state.threads_main,
+ unwinder->interpreter_addr +
(uintptr_t)unwinder->debug_offsets.interpreter_state.threads_head,
sizeof(void*),
&thread_state_addr))
{
- set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read main
thread state");
+ set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read
threads head");
return -1;
}
_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]