]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.15] gh-152020: Fix `asyncio.all_tasks()` loosing eager tasks on FT-build (GH-15202...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Wed, 24 Jun 2026 12:47:07 +0000 (14:47 +0200)
committerGitHub <noreply@github.com>
Wed, 24 Jun 2026 12:47:07 +0000 (12:47 +0000)
gh-152020: Fix `asyncio.all_tasks()` loosing eager tasks on FT-build (GH-152022)
(cherry picked from commit ad2cabfccb539dd23f9a17907bd63913013cb1e3)

Co-authored-by: Timofei <128279579+deadlovelll@users.noreply.github.com>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
Lib/test/test_asyncio/test_free_threading.py
Misc/NEWS.d/next/Core_and_Builtins/2026-06-23-19-50-22.gh-issue-152020.DTKXjR.rst [new file with mode: 0644]
Modules/_asynciomodule.c

index d874ed00bd7e7ad8d8a71d1e10a96ed5dfdfdd4b..0e149dadd7f1219adc1850501024f21d2c236da0 100644 (file)
@@ -165,6 +165,45 @@ class TestFreeThreading:
             loop.set_task_factory(self.factory)
             r.run(main())
 
+    def test_all_tasks_from_other_thread_includes_eager_tasks(self):
+        # gh-152020: all_tasks() called from another thread used to drop
+        # eager-started tasks on free-threaded builds.
+        loop = asyncio.new_event_loop()
+
+        async def wait_forever():
+            await asyncio.Event().wait()
+
+        def eager_factory(loop, coro, **kwargs):
+            return self.factory(loop, coro, eager_start=True, **kwargs)
+
+        async def setup():
+            loop.set_task_factory(eager_factory)
+            eager = loop.create_task(wait_forever(), name="EAGER")
+            loop.set_task_factory(None)
+            normal = loop.create_task(wait_forever(), name="NORMAL")
+            return eager, normal
+
+        async def teardown():
+            tasks = [t for t in asyncio.all_tasks()
+                     if t is not asyncio.current_task()]
+            for t in tasks:
+                t.cancel()
+            await asyncio.gather(*tasks, return_exceptions=True)
+
+        thread = threading.Thread(target=loop.run_forever)
+        thread.start()
+        try:
+            held = asyncio.run_coroutine_threadsafe(setup(), loop).result()
+            names = {t.get_name() for t in asyncio.all_tasks(loop)}
+            self.assertIn("NORMAL", names)
+            self.assertIn("EAGER", names)
+            del held
+        finally:
+            asyncio.run_coroutine_threadsafe(teardown(), loop).result()
+            loop.call_soon_threadsafe(loop.stop)
+            thread.join()
+            loop.close()
+
 
 class TestPyFreeThreading(TestFreeThreading, TestCase):
 
diff --git a/Misc/NEWS.d/next/Core_and_Builtins/2026-06-23-19-50-22.gh-issue-152020.DTKXjR.rst b/Misc/NEWS.d/next/Core_and_Builtins/2026-06-23-19-50-22.gh-issue-152020.DTKXjR.rst
new file mode 100644 (file)
index 0000000..93c716f
--- /dev/null
@@ -0,0 +1,3 @@
+On the free-threaded build, :func:`asyncio.all_tasks` no longer loses
+eager-started tasks when called from a thread other than the one running the
+event loop.
index 6620ee26449b1636f66b9d73c635bd5386888528..57c8d4a41a3a0dffdba6dda31c0e1533b29dba7d 100644 (file)
@@ -2366,6 +2366,11 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop,
         return -1;
     }
     _PyThreadStateImpl *ts = (_PyThreadStateImpl *)_PyThreadState_GET();
+#ifdef Py_GIL_DISABLED
+    // This is required so that _Py_TryIncref(self)
+    // works correctly in non-owning threads.
+    _PyObject_SetMaybeWeakref((PyObject *)self);
+#endif
     if (eager_start) {
         PyObject *res = PyObject_CallMethodNoArgs(loop, &_Py_ID(is_running));
         if (res == NULL) {
@@ -2384,11 +2389,6 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop,
     if (task_call_step_soon(state, self, NULL)) {
         return -1;
     }
-#ifdef Py_GIL_DISABLED
-    // This is required so that _Py_TryIncref(self)
-    // works correctly in non-owning threads.
-    _PyObject_SetMaybeWeakref((PyObject *)self);
-#endif
     register_task(ts, self);
     return 0;
 }