From 97500e3749b6a148ca8a70a1f7cf429500aef8ab Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 24 Oct 2025 18:26:40 +0530 Subject: [PATCH 1/3] Optimize asyncio.all_tasks() for the common case where the event loop is running in the current thread by avoiding stop-the-world pauses and locking. This optimization is already present for asyncio.current_task() so we do the same for asyncio.all_tasks(). --- Modules/_asynciomodule.c | 61 ++++++++++++++++++++++++---------------- 1 file changed, 37 insertions(+), 24 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 99408e60721c60..e9e6e751727b6b 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -4079,30 +4079,43 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) return NULL; } - PyInterpreterState *interp = PyInterpreterState_Get(); - // Stop the world and traverse the per-thread linked list - // of asyncio tasks for every thread, as well as the - // interpreter's linked list, and add them to `tasks`. - // The interpreter linked list is used for any lingering tasks - // whose thread state has been deallocated while the task was - // still alive. This can happen if a task is referenced by - // a different thread, in which case the task is moved to - // the interpreter's linked list from the thread's linked - // list before deallocation. See PyThreadState_Clear. - // - // The stop-the-world pause is required so that no thread - // modifies its linked list while being iterated here - // in parallel. This design allows for lock-free - // register_task/unregister_task for loops running in parallel - // in different threads (the general case). - _PyEval_StopTheWorld(interp); - int ret = add_tasks_interp(interp, (PyListObject *)tasks); - _PyEval_StartTheWorld(interp); - if (ret < 0) { - // call any escaping calls after starting the world to avoid any deadlocks. - Py_DECREF(tasks); - Py_DECREF(loop); - return NULL; + _PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET(); + if (ts->asyncio_running_loop == loop) { + // Fast path for the current running loop of current thread + // no locking or stop the world pause is required + struct llist_node *head = &ts->asyncio_tasks_head; + if (add_tasks_llist(head, (PyListObject *)tasks) < 0) { + Py_DECREF(tasks); + Py_DECREF(loop); + return NULL; + } + } else { + // Slow path for loop running in different thread + PyInterpreterState *interp = PyInterpreterState_Get(); + // Stop the world and traverse the per-thread linked list + // of asyncio tasks for every thread, as well as the + // interpreter's linked list, and add them to `tasks`. + // The interpreter linked list is used for any lingering tasks + // whose thread state has been deallocated while the task was + // still alive. This can happen if a task is referenced by + // a different thread, in which case the task is moved to + // the interpreter's linked list from the thread's linked + // list before deallocation. See PyThreadState_Clear. + // + // The stop-the-world pause is required so that no thread + // modifies its linked list while being iterated here + // in parallel. This design allows for lock-free + // register_task/unregister_task for loops running in parallel + // in different threads (the general case). + _PyEval_StopTheWorld(interp); + int ret = add_tasks_interp(interp, (PyListObject *)tasks); + _PyEval_StartTheWorld(interp); + if (ret < 0) { + // call any escaping calls after starting the world to avoid any deadlocks. + Py_DECREF(tasks); + Py_DECREF(loop); + return NULL; + } } // All the tasks are now in the list, now filter the tasks which are done From 932f6f386c358c09ebb246d325bfad36036f7020 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 24 Oct 2025 19:05:01 +0530 Subject: [PATCH 2/3] use ts->base.interp --- Modules/_asynciomodule.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index e9e6e751727b6b..65706ee7566059 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -4091,7 +4091,7 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) } } else { // Slow path for loop running in different thread - PyInterpreterState *interp = PyInterpreterState_Get(); + PyInterpreterState *interp = ts->base.interp; // Stop the world and traverse the per-thread linked list // of asyncio tasks for every thread, as well as the // interpreter's linked list, and add them to `tasks`. From 7808c2e2f79d2069b4104344a6ebd82d5c975f60 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 24 Oct 2025 19:34:02 +0530 Subject: [PATCH 3/3] Update Modules/_asynciomodule.c Co-authored-by: Sam Gross --- Modules/_asynciomodule.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 65706ee7566059..1f58b1fb3506c6 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -4089,7 +4089,8 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop) Py_DECREF(loop); return NULL; } - } else { + } + else { // Slow path for loop running in different thread PyInterpreterState *interp = ts->base.interp; // Stop the world and traverse the per-thread linked list