diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index 8af199d6dcc41a..74ea612616a727 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -205,6 +205,11 @@ def create_task(self, coro, *, name=None, context=None): else: self._tasks.add(task) task.add_done_callback(self._on_task_done) + if self._aborting: + # gh-128550: if this task is eager it might have started + # another eager task that aborts us, if so we must cancel + # this task. + 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 870fa8dbbf2714..6353a92dd4664a 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -1040,6 +1040,32 @@ class MyKeyboardInterrupt(KeyboardInterrupt): self.assertIsNotNone(exc) self.assertListEqual(gc.get_referrers(exc), no_other_refs()) + async def test_cancels_task_if_created_during_creation(self): + # regression test for gh-128550 + ran = False + class MyError(Exception): + pass + + exc = None + try: + async with asyncio.TaskGroup() as tg: + async def third_task(): + raise MyError("third task failed") + + async def second_task(): + nonlocal ran + tg.create_task(third_task()) + with self.assertRaises(asyncio.CancelledError): + await asyncio.sleep(0) # eager tasks cancel here + await asyncio.sleep(0) # lazy tasks cancel here + ran = True + + tg.create_task(second_task()) + except* MyError as excs: + exc = excs.exceptions[0] + + self.assertTrue(ran) + self.assertIsInstance(exc, MyError) class TestTaskGroup(BaseTestTaskGroup, unittest.IsolatedAsyncioTestCase): loop_factory = asyncio.EventLoop diff --git a/Misc/NEWS.d/next/Library/2025-01-06-18-58-07.gh-issue-128550.lzeWhZ.rst b/Misc/NEWS.d/next/Library/2025-01-06-18-58-07.gh-issue-128550.lzeWhZ.rst new file mode 100644 index 00000000000000..a9087f48a1356a --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-01-06-18-58-07.gh-issue-128550.lzeWhZ.rst @@ -0,0 +1 @@ +Fix a deadlock in :class:`asyncio.TaskGroup` when using eager tasks that abort the task group too early.