Skip to content

Commit 47126a7

Browse files
committed
gh-158522: Fix truncated stack for a task whose coroutine recurses
1 parent 568dd3d commit 47126a7

4 files changed

Lines changed: 31 additions & 25 deletions

File tree

‎Lib/test/test_external_inspection.py‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -458,6 +458,21 @@ async def main():
458458
self.assertIn(main_name, names)
459459
self.assertEqual([len(n) for n in names if n.startswith("x")], [255])
460460

461+
@skip_if_not_supported
462+
def test_recursive_coroutine_stack_is_not_truncated(self):
463+
# gh-158522
464+
async def rec(n):
465+
if n:
466+
return await rec(n - 1)
467+
return [
468+
frame.funcname.rpartition(".")[2]
469+
for task in RemoteUnwinder(os.getpid()).get_async_stack_trace()[0].awaited_by
470+
for coro in task.coroutine_stack
471+
for frame in coro.call_stack
472+
]
473+
474+
self.assertEqual(asyncio.run(rec(3)), ["rec"] * 4)
475+
461476
@skip_if_not_supported
462477
@unittest.skipIf(
463478
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix the sampling profiler showing a single frame for a task whose coroutine
2+
recurses. Patch by Timofei Ivankov.

‎Modules/_remote_debugging/_remote_debugging.h‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -739,7 +739,7 @@ extern int parse_async_frame_chain(
739739
RemoteUnwinderObject *unwinder,
740740
PyObject *calls,
741741
uintptr_t address_of_thread,
742-
uintptr_t running_task_code_obj
742+
uintptr_t running_task_frame
743743
);
744744

745745
/* Set iteration */
@@ -777,10 +777,10 @@ extern int find_running_task_in_thread(
777777
uintptr_t *running_task_addr
778778
);
779779

780-
extern int get_task_code_object(
780+
extern int get_task_frame(
781781
RemoteUnwinderObject *unwinder,
782782
uintptr_t task_addr,
783-
uintptr_t *code_obj_addr
783+
uintptr_t *frame_addr
784784
);
785785

786786
extern int append_awaited_by(

‎Modules/_remote_debugging/asyncio.c‎

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -724,7 +724,7 @@ find_running_task_in_thread(
724724
}
725725

726726
int
727-
get_task_code_object(RemoteUnwinderObject *unwinder, uintptr_t task_addr, uintptr_t *code_obj_addr) {
727+
get_task_frame(RemoteUnwinderObject *unwinder, uintptr_t task_addr, uintptr_t *frame_addr) {
728728
uintptr_t running_coro_addr = 0;
729729

730730
if(read_py_ptr(
@@ -741,21 +741,7 @@ get_task_code_object(RemoteUnwinderObject *unwinder, uintptr_t task_addr, uintpt
741741
return -1;
742742
}
743743

744-
// note: genobject's gi_iframe is an embedded struct so the address to
745-
// the offset leads directly to its first field: f_executable
746-
if (read_py_ptr(
747-
unwinder,
748-
running_coro_addr + (uintptr_t)unwinder->debug_offsets.gen_object.gi_iframe, code_obj_addr) < 0) {
749-
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read running task code object");
750-
return -1;
751-
}
752-
753-
if (*code_obj_addr == 0) {
754-
PyErr_SetString(PyExc_RuntimeError, "Running task code object is NULL");
755-
set_exception_cause(unwinder, PyExc_RuntimeError, "Running task code object address is NULL");
756-
return -1;
757-
}
758-
744+
*frame_addr = running_coro_addr + (uintptr_t)unwinder->debug_offsets.gen_object.gi_iframe;
759745
return 0;
760746
}
761747

@@ -768,7 +754,7 @@ parse_async_frame_chain(
768754
RemoteUnwinderObject *unwinder,
769755
PyObject *calls,
770756
uintptr_t address_of_thread,
771-
uintptr_t running_task_code_obj
757+
uintptr_t running_task_frame
772758
) {
773759
uintptr_t address_of_current_frame;
774760
if (find_running_frame(unwinder, address_of_thread, &address_of_current_frame) < 0) {
@@ -779,6 +765,8 @@ parse_async_frame_chain(
779765
while ((void*)address_of_current_frame != NULL) {
780766
PyObject* frame_info = NULL;
781767
uintptr_t address_of_code_object;
768+
769+
uintptr_t this_frame = address_of_current_frame;
782770
int res = parse_frame_object(
783771
unwinder,
784772
&frame_info,
@@ -803,8 +791,9 @@ parse_async_frame_chain(
803791
}
804792

805793
Py_DECREF(frame_info);
806-
807-
if (address_of_code_object == running_task_code_obj) {
794+
795+
// gh-158522: compare frame instead of code-object
796+
if (this_frame == running_task_frame) {
808797
break;
809798
}
810799
}
@@ -940,8 +929,8 @@ process_running_task_chain(
940929
uintptr_t thread_state_addr,
941930
PyObject *result
942931
) {
943-
uintptr_t running_task_code_obj = 0;
944-
if(get_task_code_object(unwinder, running_task_addr, &running_task_code_obj) < 0) {
932+
uintptr_t running_task_frame = 0;
933+
if(get_task_frame(unwinder, running_task_addr, &running_task_frame) < 0) {
945934
return -1;
946935
}
947936

@@ -973,7 +962,7 @@ process_running_task_chain(
973962
}
974963

975964
// Add the chain from the current frame to this task
976-
if (parse_async_frame_chain(unwinder, frame_chain, thread_state_addr, running_task_code_obj) < 0) {
965+
if (parse_async_frame_chain(unwinder, frame_chain, thread_state_addr, running_task_frame) < 0) {
977966
return -1;
978967
}
979968

0 commit comments

Comments
 (0)