]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
gh-140414: add fastpath for current running loop in `asyncio.all_tasks` (#140542)
authorKumar Aditya <kumaraditya@python.org>
Fri, 24 Oct 2025 14:32:17 +0000 (20:02 +0530)
committerGitHub <noreply@github.com>
Fri, 24 Oct 2025 14:32:17 +0000 (20:02 +0530)
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

index 99408e60721c602fe5da791467ba7a4dc6c55030..1f58b1fb3506c6c9769c41602b8c91f2ecaf9edd 100644 (file)
@@ -4079,30 +4079,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