Skip to content

Commit 95e5d59

Browse files
gh-140414: add fastpath for current running loop in asyncio.all_tasks (#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()`.
1 parent ebf9938 commit 95e5d59

File tree

1 file changed

+38
-24
lines changed

1 file changed

+38
-24
lines changed

Modules/_asynciomodule.c

Lines changed: 38 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -4079,30 +4079,44 @@ _asyncio_all_tasks_impl(PyObject *module, PyObject *loop)
40794079
return NULL;
40804080
}
40814081

4082-
PyInterpreterState *interp = PyInterpreterState_Get();
4083-
// Stop the world and traverse the per-thread linked list
4084-
// of asyncio tasks for every thread, as well as the
4085-
// interpreter's linked list, and add them to `tasks`.
4086-
// The interpreter linked list is used for any lingering tasks
4087-
// whose thread state has been deallocated while the task was
4088-
// still alive. This can happen if a task is referenced by
4089-
// a different thread, in which case the task is moved to
4090-
// the interpreter's linked list from the thread's linked
4091-
// list before deallocation. See PyThreadState_Clear.
4092-
//
4093-
// The stop-the-world pause is required so that no thread
4094-
// modifies its linked list while being iterated here
4095-
// in parallel. This design allows for lock-free
4096-
// register_task/unregister_task for loops running in parallel
4097-
// in different threads (the general case).
4098-
_PyEval_StopTheWorld(interp);
4099-
int ret = add_tasks_interp(interp, (PyListObject *)tasks);
4100-
_PyEval_StartTheWorld(interp);
4101-
if (ret < 0) {
4102-
// call any escaping calls after starting the world to avoid any deadlocks.
4103-
Py_DECREF(tasks);
4104-
Py_DECREF(loop);
4105-
return NULL;
4082+
_PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET();
4083+
if (ts->asyncio_running_loop == loop) {
4084+
// Fast path for the current running loop of current thread
4085+
// no locking or stop the world pause is required
4086+
struct llist_node *head = &ts->asyncio_tasks_head;
4087+
if (add_tasks_llist(head, (PyListObject *)tasks) < 0) {
4088+
Py_DECREF(tasks);
4089+
Py_DECREF(loop);
4090+
return NULL;
4091+
}
4092+
}
4093+
else {
4094+
// Slow path for loop running in different thread
4095+
PyInterpreterState *interp = ts->base.interp;
4096+
// Stop the world and traverse the per-thread linked list
4097+
// of asyncio tasks for every thread, as well as the
4098+
// interpreter's linked list, and add them to `tasks`.
4099+
// The interpreter linked list is used for any lingering tasks
4100+
// whose thread state has been deallocated while the task was
4101+
// still alive. This can happen if a task is referenced by
4102+
// a different thread, in which case the task is moved to
4103+
// the interpreter's linked list from the thread's linked
4104+
// list before deallocation. See PyThreadState_Clear.
4105+
//
4106+
// The stop-the-world pause is required so that no thread
4107+
// modifies its linked list while being iterated here
4108+
// in parallel. This design allows for lock-free
4109+
// register_task/unregister_task for loops running in parallel
4110+
// in different threads (the general case).
4111+
_PyEval_StopTheWorld(interp);
4112+
int ret = add_tasks_interp(interp, (PyListObject *)tasks);
4113+
_PyEval_StartTheWorld(interp);
4114+
if (ret < 0) {
4115+
// call any escaping calls after starting the world to avoid any deadlocks.
4116+
Py_DECREF(tasks);
4117+
Py_DECREF(loop);
4118+
return NULL;
4119+
}
41064120
}
41074121

41084122
// All the tasks are now in the list, now filter the tasks which are done

0 commit comments

Comments
 (0)