Skip to content

Commit de92aab

Browse files
committed
Address review
1 parent 51fa194 commit de92aab

File tree

2 files changed

+27
-8
lines changed

2 files changed

+27
-8
lines changed

Lib/test/test_external_inspection.py

+7-1
Original file line numberDiff line numberDiff line change
@@ -434,7 +434,11 @@ async def main():
434434
# no synchronization. That occasionally leads to invalid
435435
# reads. Here we avoid making the test flaky.
436436
msg = str(re)
437-
if msg.startswith("Unknown error reading memory"):
437+
if msg.startswith("Task list appears corrupted"):
438+
continue
439+
elif msg.startswith("Invalid linked list structure reading remote memory"):
440+
continue
441+
elif msg.startswith("Unknown error reading memory"):
438442
continue
439443
elif msg.startswith("Unhandled frame owner"):
440444
continue
@@ -445,6 +449,8 @@ async def main():
445449
self.assertEqual(len(all_awaited_by), 2)
446450
# expected: a tuple with the thread ID and the awaited_by list
447451
self.assertEqual(len(all_awaited_by[0]), 2)
452+
# expected: no tasks in the fallback per-interp task list
453+
self.assertEqual(all_awaited_by[1], (0, []))
448454
entries = all_awaited_by[0][1]
449455
# expected: at least 1000 pending tasks
450456
self.assertGreaterEqual(len(entries), 1000)

Modules/_testexternalinspection.c

+20-7
Original file line numberDiff line numberDiff line change
@@ -1490,13 +1490,21 @@ append_awaited_by_for_thread(
14901490
return -1;
14911491
}
14921492

1493-
size_t iteration_count = 0;
1494-
const size_t MAX_ITERATIONS = 100000; // Reasonable upper bound
1495-
while ((uintptr_t)task_node.next != head_addr) {
1496-
if (++iteration_count > MAX_ITERATIONS) {
1497-
PyErr_SetString(PyExc_RuntimeError, "Task list appears corrupted");
1498-
return -1;
1499-
}
1493+
size_t iteration_count = 0;
1494+
const size_t MAX_ITERATIONS = 2 << 15; // A reasonable upper bound
1495+
while ((uintptr_t)task_node.next != head_addr) {
1496+
if (++iteration_count > MAX_ITERATIONS) {
1497+
PyErr_SetString(PyExc_RuntimeError, "Task list appears corrupted");
1498+
return -1;
1499+
}
1500+
1501+
if (task_node.next == NULL) {
1502+
PyErr_SetString(
1503+
PyExc_RuntimeError,
1504+
"Invalid linked list structure reading remote memory");
1505+
return -1;
1506+
}
1507+
15001508
uintptr_t task_addr = (uintptr_t)task_node.next
15011509
- async_offsets->asyncio_task_object.task_node;
15021510

@@ -1698,6 +1706,11 @@ get_all_awaited_by(PyObject* self, PyObject* args)
16981706
head_addr = interpreter_state_addr
16991707
+ local_async_debug.asyncio_interpreter_state.asyncio_tasks_head;
17001708

1709+
// On top of a per-thread task lists used by default by asyncio to avoid
1710+
// contention, there is also a fallback per-interpreter list of tasks;
1711+
// any tasks still pending when a thread is destroyed will be moved to the
1712+
// per-interpreter task list. It's unlikely we'll find anything here, but
1713+
// interesting for debugging.
17011714
if (append_awaited_by(pid, 0, head_addr, &local_debug_offsets,
17021715
&local_async_debug, result))
17031716
{

0 commit comments

Comments
 (0)