From 1e9f4d5a3ebc170c6b2c454d1b3f3f901895e5ac Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Tue, 29 Sep 2026 20:36:07 +0300 Subject: [PATCH] gh-158443: Fix asyncio ps showing a running task as a single frame --- Lib/test/test_external_inspection.py | 19 +++++++++++++++++++ ...-09-29-20-34-53.gh-issue-158443.-1rc7t.rst | 2 ++ Modules/_remote_debugging/_remote_debugging.h | 2 ++ Modules/_remote_debugging/asyncio.c | 17 ++++++++++++++++- 4 files changed, 39 insertions(+), 1 deletion(-) create mode 100644 Misc/NEWS.d/next/Library/2026-09-29-20-34-53.gh-issue-158443.-1rc7t.rst diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py index c83d2cb2abeac81..1bdb14a469d38c7 100644 --- a/Lib/test/test_external_inspection.py +++ b/Lib/test/test_external_inspection.py @@ -458,6 +458,25 @@ async def main(): self.assertIn(main_name, names) self.assertEqual([len(n) for n in names if n.startswith("x")], [255]) + @skip_if_not_supported + def test_running_task_reports_its_call_stack(self): + # gh-158443 + async def inner(): + return [ + [frame.funcname.rpartition(".")[2] for frame in coro.call_stack] + for info in RemoteUnwinder(os.getpid()).get_all_awaited_by() + for task in info.awaited_by + for coro in task.coroutine_stack + ] + + async def middle(): + return await inner() + + async def main(): + return await middle() + + self.assertEqual(asyncio.run(main()), [["inner", "middle", "main"]]) + @skip_if_not_supported @unittest.skipIf( sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED, diff --git a/Misc/NEWS.d/next/Library/2026-09-29-20-34-53.gh-issue-158443.-1rc7t.rst b/Misc/NEWS.d/next/Library/2026-09-29-20-34-53.gh-issue-158443.-1rc7t.rst new file mode 100644 index 000000000000000..ddb62157286d122 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-29-20-34-53.gh-issue-158443.-1rc7t.rst @@ -0,0 +1,2 @@ +Fix ``python -m asyncio ps`` showing a task that is running as a single +frame instead of its call stack. Patch by Timofei Ivankov. diff --git a/Modules/_remote_debugging/_remote_debugging.h b/Modules/_remote_debugging/_remote_debugging.h index fa37fb7b2167ecf..8a61d8bdcd4480f 100644 --- a/Modules/_remote_debugging/_remote_debugging.h +++ b/Modules/_remote_debugging/_remote_debugging.h @@ -402,6 +402,8 @@ typedef struct { pid_t *thread_tids; // Reusable buffer for thread IDs size_t thread_tids_capacity; // Current capacity of thread_tids buffer #endif + // Thread whose task list is being walked, 0 when there is none + uintptr_t walked_thread_addr; } RemoteUnwinderObject; #define RemoteUnwinder_CAST(op) ((RemoteUnwinderObject *)(op)) diff --git a/Modules/_remote_debugging/asyncio.c b/Modules/_remote_debugging/asyncio.c index 44a9a3cbce0061a..dd3be90be09255b 100644 --- a/Modules/_remote_debugging/asyncio.c +++ b/Modules/_remote_debugging/asyncio.c @@ -363,6 +363,17 @@ parse_coro_chain( return 0; } + if (frame_state == FRAME_EXECUTING && unwinder->walked_thread_addr) { + // gh-158443: a running coroutine keeps its inner frames on the thread stack + Py_DECREF(name); + if (parse_async_frame_chain(unwinder, render_to, + unwinder->walked_thread_addr, + address_of_code_object) < 0) { + return -1; + } + return PyList_Reverse(render_to); + } + if (PyList_Append(render_to, name)) { Py_DECREF(name); set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to append frame to coro chain"); @@ -930,7 +941,11 @@ process_thread_for_awaited_by( ) { PyObject *result = (PyObject *)context; uintptr_t head_addr = thread_state_addr + (uintptr_t)unwinder->async_debug_offsets.asyncio_thread_state.asyncio_tasks_head; - return append_awaited_by(unwinder, tid, head_addr, result); + + unwinder->walked_thread_addr = thread_state_addr; + int res = append_awaited_by(unwinder, tid, head_addr, result); + unwinder->walked_thread_addr = 0; + return res; } static int