Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions Lib/test/test_free_threading/test_asyncio.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import asyncio
import threading
import unittest
from unittest import TestCase

from test.support import threading_helper


async def _forever():
await asyncio.Event().wait()


@threading_helper.requires_working_threading()
class TestAllTasks(TestCase):
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 setup():
loop.set_task_factory(asyncio.eager_task_factory)
loop.create_task(_forever(), name="EAGER")
loop.set_task_factory(None)
loop.create_task(_forever(), name="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:
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)
finally:
asyncio.run_coroutine_threadsafe(teardown(), loop).result()
loop.call_soon_threadsafe(loop.stop)
thread.join()
loop.close()


if __name__ == "__main__":
unittest.main()
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
On the free-threaded build, :func:`asyncio.all_tasks` no longer lost
eager-started tasks when called from a thread other than the one running the
event loop
3 changes: 3 additions & 0 deletions Modules/_asynciomodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -2190,6 +2190,9 @@ register_task(_PyThreadStateImpl *ts, TaskObj *task)
assert(task->task_node.prev != NULL);
return;
}
#ifdef Py_GIL_DISABLED
_PyObject_SetMaybeWeakref((PyObject *)task);
#endif
struct llist_node *head = &ts->asyncio_tasks_head;
llist_insert_tail(head, &task->task_node);
}
Expand Down
Loading