https://github.com/python/cpython/commit/05302c0ad6d909badf3c5c4c5745d398901a2465 commit: 05302c0ad6d909badf3c5c4c5745d398901a2465 branch: main author: Timofei Ivankov <[email protected]> committer: pablogsal <[email protected]> date: 2026-10-05T16:46:39+01:00 summary:
gh-158522: Fix truncated stack for a task whose coroutine recurses (#158526) * gh-158522: Fix truncated stack for a task whose coroutine recurses * fix lint * Apply suggestion from @maurycy Co-authored-by: Maurycy Pawłowski-Wieroński <[email protected]> * Update Misc/NEWS.d/next/Library/2026-09-30-20-09-46.gh-issue-158522.L6j7tU.rst Co-authored-by: Maurycy Pawłowski-Wieroński <[email protected]> * Fix line length in the new test --------- Co-authored-by: Maurycy Pawłowski-Wieroński <[email protected]> files: A Misc/NEWS.d/next/Library/2026-09-30-20-09-46.gh-issue-158522.L6j7tU.rst M Lib/test/test_external_inspection.py M Modules/_remote_debugging/_remote_debugging.h M Modules/_remote_debugging/asyncio.c diff --git a/Lib/test/test_external_inspection.py b/Lib/test/test_external_inspection.py index 0c189e60a93b8e..9fff8e8bff91f7 100644 --- a/Lib/test/test_external_inspection.py +++ b/Lib/test/test_external_inspection.py @@ -492,6 +492,22 @@ 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_recursive_coroutine_stack_is_not_truncated(self): + # gh-158522 + async def rec(n): + if n: + return await rec(n - 1) + return [ + frame.funcname.rpartition(".")[2] + for task in RemoteUnwinder( + os.getpid()).get_async_stack_trace()[0].awaited_by + for coro in task.coroutine_stack + for frame in coro.call_stack + ] + + self.assertEqual(asyncio.run(rec(3)), ["rec"] * 4) + @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-30-20-09-46.gh-issue-158522.L6j7tU.rst b/Misc/NEWS.d/next/Library/2026-09-30-20-09-46.gh-issue-158522.L6j7tU.rst new file mode 100644 index 00000000000000..30c2df4bacd4a1 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-09-30-20-09-46.gh-issue-158522.L6j7tU.rst @@ -0,0 +1,2 @@ +Fix :mod:`profiling.sampling` showing a single frame for a task whose +coroutine recurses. Patch by Timofei Ivankov. diff --git a/Modules/_remote_debugging/_remote_debugging.h b/Modules/_remote_debugging/_remote_debugging.h index c57b30533ce1c1..79fa4a92e745bd 100644 --- a/Modules/_remote_debugging/_remote_debugging.h +++ b/Modules/_remote_debugging/_remote_debugging.h @@ -729,7 +729,7 @@ extern int parse_async_frame_chain( RemoteUnwinderObject *unwinder, PyObject *calls, uintptr_t address_of_thread, - uintptr_t running_task_code_obj + uintptr_t running_task_frame ); extern int process_single_task_node( @@ -745,10 +745,10 @@ extern int find_running_task_in_thread( uintptr_t *running_task_addr ); -extern int get_task_code_object( +extern int get_task_frame( RemoteUnwinderObject *unwinder, uintptr_t task_addr, - uintptr_t *code_obj_addr + uintptr_t *frame_addr ); extern int append_awaited_by( diff --git a/Modules/_remote_debugging/asyncio.c b/Modules/_remote_debugging/asyncio.c index aa3f5b638931f8..c8d79774a47a73 100644 --- a/Modules/_remote_debugging/asyncio.c +++ b/Modules/_remote_debugging/asyncio.c @@ -716,7 +716,7 @@ find_running_task_in_thread( } int -get_task_code_object(RemoteUnwinderObject *unwinder, uintptr_t task_addr, uintptr_t *code_obj_addr) { +get_task_frame(RemoteUnwinderObject *unwinder, uintptr_t task_addr, uintptr_t *frame_addr) { uintptr_t running_coro_addr = 0; if(read_py_ptr( @@ -733,21 +733,7 @@ get_task_code_object(RemoteUnwinderObject *unwinder, uintptr_t task_addr, uintpt return -1; } - // note: genobject's gi_iframe is an embedded struct so the address to - // the offset leads directly to its first field: f_executable - if (read_py_ptr( - unwinder, - running_coro_addr + (uintptr_t)unwinder->debug_offsets.gen_object.gi_iframe, code_obj_addr) < 0) { - set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task code object"); - return -1; - } - - if (*code_obj_addr == 0) { - PyErr_SetString(PyExc_RuntimeError, "Running task code object is NULL"); - set_exception_cause(unwinder, PyExc_RuntimeError, "Running task code object address is NULL"); - return -1; - } - + *frame_addr = running_coro_addr + (uintptr_t)unwinder->debug_offsets.gen_object.gi_iframe; return 0; } @@ -760,7 +746,7 @@ parse_async_frame_chain( RemoteUnwinderObject *unwinder, PyObject *calls, uintptr_t address_of_thread, - uintptr_t running_task_code_obj + uintptr_t running_task_frame ) { uintptr_t address_of_current_frame; if (find_running_frame(unwinder, address_of_thread, &address_of_current_frame) < 0) { @@ -777,6 +763,8 @@ parse_async_frame_chain( } PyObject* frame_info = NULL; uintptr_t address_of_code_object; + + uintptr_t this_frame = address_of_current_frame; int res = parse_frame_object( unwinder, &frame_info, @@ -802,7 +790,9 @@ parse_async_frame_chain( Py_DECREF(frame_info); - if (address_of_code_object == running_task_code_obj) { + // Stop at the task's own frame. Code objects are shared by + // recursive calls, so they cannot identify it. + if (this_frame == running_task_frame) { break; } } @@ -938,8 +928,8 @@ process_running_task_chain( uintptr_t thread_state_addr, PyObject *result ) { - uintptr_t running_task_code_obj = 0; - if(get_task_code_object(unwinder, running_task_addr, &running_task_code_obj) < 0) { + uintptr_t running_task_frame = 0; + if(get_task_frame(unwinder, running_task_addr, &running_task_frame) < 0) { return -1; } @@ -971,7 +961,7 @@ process_running_task_chain( } // Add the chain from the current frame to this task - if (parse_async_frame_chain(unwinder, frame_chain, thread_state_addr, running_task_code_obj) < 0) { + if (parse_async_frame_chain(unwinder, frame_chain, thread_state_addr, running_task_frame) < 0) { 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]
