Skip to content

Commit ef51563

Browse files
committed
task-waiter iterative bfs walk
1 parent 10149e9 commit ef51563

3 files changed

Lines changed: 40 additions & 120 deletions

File tree

Lib/test/test_external_inspection.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3809,9 +3809,9 @@ class TestFrameChainLimits(RemoteInspectionTestBase):
38093809
"""Frame chain walks abort instead of looping/overflowing on deep chains."""
38103810

38113811
# Limits plus one, to exceed them (must match MAX_FRAME_CHAIN_DEPTH /
3812-
# MAX_TASK_WAITER_CHAIN_DEPTH from _remote_debugging.h)
3812+
# MAX_TASK_WAITER_WALK_TASKS from _remote_debugging.h)
38133813
FRAME_CHAIN_DEPTH = 1024 + 512 + 1
3814-
TASK_WAITER_CHAIN_DEPTH = 256 + 1
3814+
TASK_WAITER_WALK_TASKS = 2**16 + 1
38153815

38163816
def _assert_unwinder_limit_error(self, unwind, expected_substring):
38173817
"""Call unwind() until it raises the frame chain limit error.
@@ -3886,7 +3886,7 @@ async def chain(n):
38863886
task = asyncio.create_task(chain(n - 1))
38873887
await task
38883888
3889-
asyncio.run(chain({self.TASK_WAITER_CHAIN_DEPTH}))
3889+
asyncio.run(chain({self.TASK_WAITER_WALK_TASKS}))
38903890
"""
38913891
with self._target_process(script_body) as (p, client_socket, _):
38923892
_wait_for_signal(client_socket, b"ready")

Modules/_remote_debugging/_remote_debugging.h

Lines changed: 1 addition & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -148,7 +148,7 @@ typedef enum _WIN32_THREADSTATE {
148148
#define MAX_LONG_DIGITS 64 /* Allows values up to ~2^1920 */
149149
#define MAX_SET_TABLE_SIZE (1 << 20) /* 1 million entries max for set iteration */
150150
#define MAX_FRAME_CHAIN_DEPTH (1024 + 512) /* Iteration bound for frame chain walks */
151-
#define MAX_TASK_WAITER_CHAIN_DEPTH 256 /* Task waiter walk recursion cap: 256 * SIZEOF_TASK_OBJ ~= 1 MiB */
151+
#define MAX_TASK_WAITER_WALK_TASKS (1 << 16) /* Total-task bound for waiter walks */
152152

153153
#ifndef MAX
154154
#define MAX(a, b) ((a) > (b) ? (a) : (b))
@@ -480,13 +480,6 @@ typedef int (*thread_processor_func)(
480480
void *context
481481
);
482482

483-
typedef int (*set_entry_processor_func)(
484-
RemoteUnwinderObject *unwinder,
485-
uintptr_t key_addr,
486-
void *context,
487-
size_t depth
488-
);
489-
490483
typedef int (*interpreter_processor_func)(
491484
RuntimeOffsets *offsets,
492485
uintptr_t interpreter_state_addr,
@@ -746,38 +739,13 @@ extern int parse_async_frame_chain(
746739
uintptr_t running_task_code_obj
747740
);
748741

749-
/* Set iteration */
750-
extern int iterate_set_entries(
751-
RemoteUnwinderObject *unwinder,
752-
uintptr_t set_addr,
753-
set_entry_processor_func processor,
754-
void *context,
755-
size_t depth
756-
);
757-
758-
/* Task awaited_by processing */
759-
extern int process_task_awaited_by(
760-
RemoteUnwinderObject *unwinder,
761-
uintptr_t task_address,
762-
set_entry_processor_func processor,
763-
void *context,
764-
size_t depth
765-
);
766-
767742
extern int process_single_task_node(
768743
RemoteUnwinderObject *unwinder,
769744
uintptr_t task_addr,
770745
PyObject **task_info,
771746
PyObject *result
772747
);
773748

774-
extern int process_task_and_waiters(
775-
RemoteUnwinderObject *unwinder,
776-
uintptr_t task_addr,
777-
PyObject *result,
778-
size_t depth
779-
);
780-
781749
extern int find_running_task_in_thread(
782750
RemoteUnwinderObject *unwinder,
783751
uintptr_t thread_state_addr,

Modules/_remote_debugging/asyncio.c

Lines changed: 36 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -116,13 +116,11 @@ ensure_async_debug_offsets(RemoteUnwinderObject *unwinder)
116116
* SET ITERATION FUNCTIONS
117117
* ============================================================================ */
118118

119-
int
119+
static int
120120
iterate_set_entries(
121121
RemoteUnwinderObject *unwinder,
122122
uintptr_t set_addr,
123-
set_entry_processor_func processor,
124-
void *context,
125-
size_t depth
123+
PyObject *awaited_by
126124
) {
127125
char set_object[SIZEOF_SET_OBJ];
128126
if (_Py_RemoteDebug_PagedReadRemoteMemory(&unwinder->handle, set_addr,
@@ -154,19 +152,10 @@ iterate_set_entries(
154152
}
155153

156154
if ((void*)key_addr != NULL) {
157-
Py_ssize_t ref_cnt;
158-
if (read_Py_ssize_t(unwinder, table_ptr, &ref_cnt) < 0) {
159-
set_exception_cause(unwinder, PyExc_RuntimeError, "Failed to read set entry ref count");
155+
if (parse_task(unwinder, key_addr, awaited_by) < 0) {
160156
return -1;
161157
}
162-
163-
if (ref_cnt) {
164-
// Process this valid set entry
165-
if (processor(unwinder, key_addr, context, depth) < 0) {
166-
return -1;
167-
}
168-
els++;
169-
}
158+
els++;
170159
}
171160
table_ptr += sizeof(void*) * 2;
172161
i++;
@@ -526,44 +515,11 @@ parse_task(
526515
* TASK AWAITED_BY PROCESSING
527516
* ============================================================================ */
528517

529-
// Forward declaration for mutual recursion
530-
static int process_waiter_task(
531-
RemoteUnwinderObject *unwinder,
532-
uintptr_t key_addr,
533-
void *context,
534-
size_t depth
535-
);
536-
537-
// Processor function for parsing tasks in sets
538-
static int
539-
process_task_parser(
540-
RemoteUnwinderObject *unwinder,
541-
uintptr_t key_addr,
542-
void *context,
543-
size_t depth
544-
) {
545-
(void)depth;
546-
PyObject *awaited_by = (PyObject *)context;
547-
return parse_task(unwinder, key_addr, awaited_by);
548-
}
549-
550518
static int
551519
parse_task_awaited_by(
552520
RemoteUnwinderObject *unwinder,
553521
uintptr_t task_address,
554522
PyObject *awaited_by
555-
) {
556-
return process_task_awaited_by(
557-
unwinder, task_address, process_task_parser, awaited_by, 0);
558-
}
559-
560-
int
561-
process_task_awaited_by(
562-
RemoteUnwinderObject *unwinder,
563-
uintptr_t task_address,
564-
set_entry_processor_func processor,
565-
void *context,
566-
size_t depth
567523
) {
568524
// Read the entire TaskObj at once
569525
char task_obj[SIZEOF_TASK_OBJ];
@@ -582,11 +538,10 @@ process_task_awaited_by(
582538
char awaited_by_is_a_set = GET_MEMBER(char, task_obj, unwinder->async_debug_offsets.asyncio_task_object.task_awaited_by_is_set);
583539

584540
if (awaited_by_is_a_set) {
585-
return iterate_set_entries(
586-
unwinder, task_ab_addr, processor, context, depth);
541+
return iterate_set_entries(unwinder, task_ab_addr, awaited_by);
587542
} else {
588543
// Single task waiting
589-
return processor(unwinder, task_ab_addr, context, depth);
544+
return parse_task(unwinder, task_ab_addr, awaited_by);
590545
}
591546
}
592547

@@ -681,40 +636,39 @@ process_single_task_node(
681636
return -1;
682637
}
683638

684-
int
685-
process_task_and_waiters(
639+
static int
640+
process_task_waiters(
686641
RemoteUnwinderObject *unwinder,
687-
uintptr_t task_addr,
688-
PyObject *result,
689-
size_t depth
642+
PyObject *result
690643
) {
691-
if (depth >= MAX_TASK_WAITER_CHAIN_DEPTH) {
692-
PyErr_SetString(PyExc_RuntimeError,
693-
"Too many task waiters (possible infinite loop)");
694-
set_exception_cause(unwinder, PyExc_RuntimeError,
695-
"Task waiter chain depth limit exceeded");
696-
return -1;
697-
}
698-
699-
// First, add this task to the result
700-
if (process_single_task_node(unwinder, task_addr, NULL, result) < 0) {
701-
return -1;
644+
for (Py_ssize_t i = 0; i < PyList_GET_SIZE(result); i++) {
645+
PyObject *task_info = PyList_GET_ITEM(result, i);
646+
PyObject *waiters = PyStructSequence_GET_ITEM(task_info, 3);
647+
for (Py_ssize_t j = 0; j < PyList_GET_SIZE(waiters); j++) {
648+
if (PyList_GET_SIZE(result) >= MAX_TASK_WAITER_WALK_TASKS) {
649+
PyErr_SetString(PyExc_RuntimeError,
650+
"Too many task waiters (possible infinite loop)");
651+
set_exception_cause(unwinder, PyExc_RuntimeError,
652+
"Task waiter walk size limit exceeded");
653+
return -1;
654+
}
655+
PyObject *waiter = PyList_GET_ITEM(waiters, j);
656+
PyObject *task_id = PyStructSequence_GET_ITEM(waiter, 1);
657+
void *task_ptr = PyLong_AsVoidPtr(task_id);
658+
if (task_ptr == NULL && PyErr_Occurred()) {
659+
set_exception_cause(unwinder, PyExc_RuntimeError,
660+
"Failed to parse waiter task ID");
661+
return -1;
662+
}
663+
if (process_single_task_node(
664+
unwinder, (uintptr_t)task_ptr, NULL, result) < 0)
665+
{
666+
return -1;
667+
}
668+
}
702669
}
703670

704-
// Now find all tasks that are waiting for this task and process them
705-
return process_task_awaited_by(
706-
unwinder, task_addr, process_waiter_task, result, depth + 1);
707-
}
708-
709-
// Processor function for task waiters
710-
static int
711-
process_waiter_task(
712-
RemoteUnwinderObject *unwinder,
713-
uintptr_t key_addr,
714-
void *context,
715-
size_t depth
716-
) {
717-
return process_task_and_waiters(unwinder, key_addr, (PyObject *)context, depth);
671+
return 0;
718672
}
719673

720674
/* ============================================================================
@@ -1017,9 +971,7 @@ process_running_task_chain(
1017971
}
1018972

1019973
// Now find all tasks that are waiting for this task and process them
1020-
if (process_task_awaited_by(
1021-
unwinder, running_task_addr, process_waiter_task, result, 1) < 0)
1022-
{
974+
if (process_task_waiters(unwinder, result) < 0) {
1023975
return -1;
1024976
}
1025977

0 commit comments

Comments
 (0)