From f0a4c0d71d7b595657531f8792ba138d9a230cce Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Fri, 24 Oct 2025 20:02:17 +0530 Subject: [PATCH 1/2] gh-140414: add fastpath for current running loop in `asyncio.all_tasks` (GH-140542) 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()`. (cherry picked from commit 95e5d596308620acbd860ec25a40ef95c2b62eaa) Co-authored-by: Kumar Aditya --- Modules/_asynciomodule.c | 62 ++++++++++++++++++++++++---------------- 1 file changed, 38 insertions(+), 24 deletions(-) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 7ff3aeff2c7669..6ec50b15200d6f 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -4075,30 +4075,44 @@ _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 = 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`. + // 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 d5b402320ff53728522fc257f159516aceb17900 Mon Sep 17 00:00:00 2001 From: Kumar Aditya Date: Thu, 5 Feb 2026 13:17:16 +0530 Subject: [PATCH 2/2] add news --- .../next/Library/2026-02-05-13-16-57.gh-issue-144494.SmcsR3.rst | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-02-05-13-16-57.gh-issue-144494.SmcsR3.rst diff --git a/Misc/NEWS.d/next/Library/2026-02-05-13-16-57.gh-issue-144494.SmcsR3.rst b/Misc/NEWS.d/next/Library/2026-02-05-13-16-57.gh-issue-144494.SmcsR3.rst new file mode 100644 index 00000000000000..d96f073ddc3aad --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-02-05-13-16-57.gh-issue-144494.SmcsR3.rst @@ -0,0 +1,2 @@ +Fix performance regression in :func:`asyncio.all_tasks` on +:term:`free-threaded builds `. Patch by Kumar Aditya.