@@ -1490,13 +1490,21 @@ append_awaited_by_for_thread(
1490
1490
return -1 ;
1491
1491
}
1492
1492
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
+
1500
1508
uintptr_t task_addr = (uintptr_t )task_node .next
1501
1509
- async_offsets -> asyncio_task_object .task_node ;
1502
1510
@@ -1698,6 +1706,11 @@ get_all_awaited_by(PyObject* self, PyObject* args)
1698
1706
head_addr = interpreter_state_addr
1699
1707
+ local_async_debug .asyncio_interpreter_state .asyncio_tasks_head ;
1700
1708
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.
1701
1714
if (append_awaited_by (pid , 0 , head_addr , & local_debug_offsets ,
1702
1715
& local_async_debug , result ))
1703
1716
{
0 commit comments