Skip to content

Commit c9f75eb

Browse files
authored
[3.14] gh-152020: Fix asyncio.all_tasks() loosing eager tasks on FT-build (GH-152022) (#152078)
[3.14] gh-152020: Fix `asyncio.all_tasks()` loosing eager tasks on FT-build (#152022)
1 parent a88629a commit c9f75eb

3 files changed

Lines changed: 47 additions & 6 deletions

File tree

Lib/test/test_asyncio/test_free_threading.py

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,45 @@ async def main():
165165
loop.set_task_factory(self.factory)
166166
r.run(main())
167167

168+
def test_all_tasks_from_other_thread_includes_eager_tasks(self):
169+
# gh-152020: all_tasks() called from another thread used to drop
170+
# eager-started tasks on free-threaded builds.
171+
loop = asyncio.new_event_loop()
172+
173+
async def wait_forever():
174+
await asyncio.Event().wait()
175+
176+
def eager_factory(loop, coro, **kwargs):
177+
return self.factory(loop, coro, eager_start=True, **kwargs)
178+
179+
async def setup():
180+
loop.set_task_factory(eager_factory)
181+
eager = loop.create_task(wait_forever(), name="EAGER")
182+
loop.set_task_factory(None)
183+
normal = loop.create_task(wait_forever(), name="NORMAL")
184+
return eager, normal
185+
186+
async def teardown():
187+
tasks = [t for t in asyncio.all_tasks()
188+
if t is not asyncio.current_task()]
189+
for t in tasks:
190+
t.cancel()
191+
await asyncio.gather(*tasks, return_exceptions=True)
192+
193+
thread = threading.Thread(target=loop.run_forever)
194+
thread.start()
195+
try:
196+
held = asyncio.run_coroutine_threadsafe(setup(), loop).result()
197+
names = {t.get_name() for t in asyncio.all_tasks(loop)}
198+
self.assertIn("NORMAL", names)
199+
self.assertIn("EAGER", names)
200+
del held
201+
finally:
202+
asyncio.run_coroutine_threadsafe(teardown(), loop).result()
203+
loop.call_soon_threadsafe(loop.stop)
204+
thread.join()
205+
loop.close()
206+
168207

169208
class TestPyFreeThreading(TestFreeThreading, TestCase):
170209

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
On the free-threaded build, :func:`asyncio.all_tasks` no longer loses
2+
eager-started tasks when called from a thread other than the one running the
3+
event loop.

Modules/_asynciomodule.c

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2385,7 +2385,11 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop,
23852385
if (self->task_name == NULL) {
23862386
return -1;
23872387
}
2388-
2388+
#ifdef Py_GIL_DISABLED
2389+
// This is required so that _Py_TryIncref(self)
2390+
// works correctly in non-owning threads.
2391+
_PyObject_SetMaybeWeakref((PyObject *)self);
2392+
#endif
23892393
if (eager_start) {
23902394
PyObject *res = PyObject_CallMethodNoArgs(loop, &_Py_ID(is_running));
23912395
if (res == NULL) {
@@ -2404,11 +2408,6 @@ _asyncio_Task___init___impl(TaskObj *self, PyObject *coro, PyObject *loop,
24042408
if (task_call_step_soon(state, self, NULL)) {
24052409
return -1;
24062410
}
2407-
#ifdef Py_GIL_DISABLED
2408-
// This is required so that _Py_TryIncref(self)
2409-
// works correctly in non-owning threads.
2410-
_PyObject_SetMaybeWeakref((PyObject *)self);
2411-
#endif
24122411
register_task(self);
24132412
return 0;
24142413
}

0 commit comments

Comments
 (0)