https://github.com/python/cpython/commit/f4239df276a23277c37f449bc8633b3ecd40fa06 commit: f4239df276a23277c37f449bc8633b3ecd40fa06 branch: 3.14 author: Miss Islington (bot) <[email protected]> committer: kumaraditya303 <[email protected]> date: 2026-02-06T08:48:06+05:30 summary:
[3.14] gh-140414: add fastpath for current running loop in `asyncio.all_tasks` (GH-140542) (#144494) * 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 <[email protected]> files: A Misc/NEWS.d/next/Library/2026-02-05-13-16-57.gh-issue-144494.SmcsR3.rst M Modules/_asynciomodule.c 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 <free-threaded build>`. Patch by Kumar Aditya. 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 _______________________________________________ Python-checkins mailing list -- [email protected] To unsubscribe send an email to [email protected] https://mail.python.org/mailman3//lists/python-checkins.python.org Member address: [email protected]
