From ba561895cbc07068c22e6e1b2718fc5a18e6c6bc Mon Sep 17 00:00:00 2001 From: Timofei Ivankov <128279579+deadlovelll@users.noreply.github.com> Date: Mon, 31 Aug 2026 21:24:03 +0300 Subject: [PATCH 1/2] gh-155418: Fix TaskGroup hang when a task cancels it before suspending --- Lib/asyncio/taskgroups.py | 3 ++ Lib/test/test_asyncio/test_taskgroups.py | 32 +++++++++++++++++++ ...-08-09-14-27-23.gh-issue-155418.kCXUIG.rst | 2 ++ 3 files changed, 37 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2026-08-09-14-27-23.gh-issue-155418.kCXUIG.rst diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index a431b45a19489d5..1fdffcc81a88655 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -222,6 +222,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 e34c552fc19ff80..99fb9b55c6b6fe7 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -1154,6 +1154,38 @@ 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(): + try: + await asyncio.sleep(10) + finally: + await asyncio.sleep(0.1) + + async def body(): + async with asyncio.TaskGroup() as tg: + tg.create_task(child()) + await asyncio.sleep(0) + tg.cancel() + + task = asyncio.create_task(body()) + await asyncio.sleep(0.01) + task.cancel('message') + with self.assertRaises(asyncio.CancelledError) as cm: + await task + self.assertEqual('message', cm.exception.args[0]) + async def test_taskgroup_cancel_before_exception(self): async def raise_exc(parent_tg: asyncio.TaskGroup): parent_tg.cancel() 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. From b60728b69a4c8aa622cf4e08a1bbde2c5272d950 Mon Sep 17 00:00:00 2001 From: Timofey Ivankov Date: Tue, 1 Sep 2026 13:47:15 +0300 Subject: [PATCH 2/2] Drop test for gh-155433 --- Lib/test/test_asyncio/test_taskgroups.py | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index 99fb9b55c6b6fe7..b1bff6b476c5dd0 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -1165,27 +1165,6 @@ async def child(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(): - try: - await asyncio.sleep(10) - finally: - await asyncio.sleep(0.1) - - async def body(): - async with asyncio.TaskGroup() as tg: - tg.create_task(child()) - await asyncio.sleep(0) - tg.cancel() - - task = asyncio.create_task(body()) - await asyncio.sleep(0.01) - task.cancel('message') - with self.assertRaises(asyncio.CancelledError) as cm: - await task - self.assertEqual('message', cm.exception.args[0]) - async def test_taskgroup_cancel_before_exception(self): async def raise_exc(parent_tg: asyncio.TaskGroup): parent_tg.cancel()