diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index 955e8e677eac1a5..2debb4d5849e682 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -239,6 +239,9 @@ def create_task(self, coro, **kwargs): # the current task too early. gh-128550, gh-128588 self._tasks.add(task) task.add_done_callback(self._on_task_done) + # gh-155418: an eager task can cancel the group before joining _tasks + if self._aborting and not task.done(): + task.cancel() try: return task finally: diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index 983e1a7dc53e6f5..1515672393816b4 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -1187,6 +1187,17 @@ async def test_taskgroup_cancel_before_create_task(self): with self.assertRaises(RuntimeError): tg.create_task(asyncio.sleep(1)) + async def test_taskgroup_cancel_from_child_before_first_suspension(self): + # gh-155418: an eager task can cancel the group before joining _tasks + async def child(tg): + tg.cancel() + await asyncio.sleep(10) + self.fail("the child was not cancelled") + + async with asyncio.TaskGroup() as tg: + task = tg.create_task(child(tg)) + self.assertTrue(task.cancelled()) + async def test_taskgroup_cancel_keeps_outer_cancellation(self): # gh-155433: any cancellation from outside the group must propagate. async def child(): diff --git a/Misc/NEWS.d/next/Library/2026-08-09-14-27-23.gh-issue-155418.kCXUIG.rst b/Misc/NEWS.d/next/Library/2026-08-09-14-27-23.gh-issue-155418.kCXUIG.rst new file mode 100644 index 000000000000000..7fe30ddb1ce8b79 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-08-09-14-27-23.gh-issue-155418.kCXUIG.rst @@ -0,0 +1,2 @@ +Fix :class:`asyncio.TaskGroup` hang when a task created by +:func:`asyncio.eager_task_factory` cancels the group before suspending.