[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 95e5d59630)

Co-authored-by: Kumar Aditya <kumaraditya@python.org>
This commit is contained in:
Miss Islington (bot) 2026-02-06 04:18:06 +01:00 committed by GitHub
parent 6614a3c30c
commit f4239df276
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 40 additions and 24 deletions

View file

@ -0,0 +1,2 @@
Fix performance regression in :func:`asyncio.all_tasks` on
:term:`free-threaded builds <free-threaded build>`. Patch by Kumar Aditya.

View file

@ -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