From 9726c3d31f4290c164fd0f8a05e4c739817d7256 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 6 Jan 2025 16:03:27 +0000 Subject: [PATCH 1/8] gh-128550: TaskGroup: cancel tasks added to set after aborting --- Lib/asyncio/taskgroups.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index 9fa772ca9d02cc..02a84b01a9e6fb 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -205,6 +205,8 @@ 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: + task.cancel() return task # Since Python 3.8 Tasks propagate all exceptions correctly, From 98b6d018902243f4d6d1b8092816a8a3ab83d483 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 6 Jan 2025 18:30:20 +0000 Subject: [PATCH 2/8] add test --- Lib/test/test_asyncio/test_taskgroups.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index c47bf4ec9ed64b..5a73afe842c138 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -997,6 +997,30 @@ 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): + ran = False + class MyError(Exception): + pass + + 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.assertIsInstance(exc, MyError) + self.assertTrue(ran) if __name__ == "__main__": unittest.main() From f9838343a6bcfdb77f85e3a6e2b06265b62c3f29 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 6 Jan 2025 18:33:04 +0000 Subject: [PATCH 3/8] Update Lib/asyncio/taskgroups.py --- Lib/asyncio/taskgroups.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py index 02a84b01a9e6fb..2fcdac54235b46 100644 --- a/Lib/asyncio/taskgroups.py +++ b/Lib/asyncio/taskgroups.py @@ -206,6 +206,9 @@ def create_task(self, coro, *, name=None, context=None): 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() return task From 6f661be392258d9eda243d142247908d479ad03f Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 6 Jan 2025 18:33:43 +0000 Subject: [PATCH 4/8] Update Lib/test/test_asyncio/test_taskgroups.py --- Lib/test/test_asyncio/test_taskgroups.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index 5a73afe842c138..a53335ab0c4761 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -998,6 +998,7 @@ class MyKeyboardInterrupt(KeyboardInterrupt): 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 From 1f3e02827d95cc29c77b6975bf6ad7dc37900e31 Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Mon, 6 Jan 2025 18:58:11 +0000 Subject: [PATCH 5/8] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20blu?= =?UTF-8?q?rb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../next/Library/2025-01-06-18-58-07.gh-issue-128550.lzeWhZ.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2025-01-06-18-58-07.gh-issue-128550.lzeWhZ.rst 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..7dbe6019a58982 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2025-01-06-18-58-07.gh-issue-128550.lzeWhZ.rst @@ -0,0 +1 @@ +Fix an edge case issue in :class:`asyncio.TaskGroup` when an eager task creates another eager task that raises an exception and aborts the group before the former task is added to that group. From b1d15b645d33d8d9b8edd0aa8de5ed4bd2cf3ff3 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 6 Jan 2025 21:21:13 +0000 Subject: [PATCH 6/8] Update Lib/test/test_asyncio/test_taskgroups.py --- Lib/test/test_asyncio/test_taskgroups.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index a53335ab0c4761..28c79aa9e64cff 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -1003,6 +1003,7 @@ async def test_cancels_task_if_created_during_creation(self): class MyError(Exception): pass + exc = None try: async with asyncio.TaskGroup() as tg: async def third_task(): From b3a20db05a04761cca8a3b407bc5131f81d083d1 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 6 Jan 2025 21:23:13 +0000 Subject: [PATCH 7/8] Apply suggestions from code review --- Lib/test/test_asyncio/test_taskgroups.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py index 28c79aa9e64cff..e907bf11e304f8 100644 --- a/Lib/test/test_asyncio/test_taskgroups.py +++ b/Lib/test/test_asyncio/test_taskgroups.py @@ -1021,8 +1021,8 @@ async def second_task(): except* MyError as excs: exc = excs.exceptions[0] - self.assertIsInstance(exc, MyError) self.assertTrue(ran) + self.assertIsInstance(exc, MyError) if __name__ == "__main__": unittest.main() From b777cf4e6d6e293f9820eae9620deaa3e9033e02 Mon Sep 17 00:00:00 2001 From: Thomas Grainger Date: Mon, 6 Jan 2025 21:28:29 +0000 Subject: [PATCH 8/8] Update Misc/NEWS.d/next/Library/2025-01-06-18-58-07.gh-issue-128550.lzeWhZ.rst Co-authored-by: Peter Bierma --- .../next/Library/2025-01-06-18-58-07.gh-issue-128550.lzeWhZ.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 index 7dbe6019a58982..a9087f48a1356a 100644 --- 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 @@ -1 +1 @@ -Fix an edge case issue in :class:`asyncio.TaskGroup` when an eager task creates another eager task that raises an exception and aborts the group before the former task is added to that group. +Fix a deadlock in :class:`asyncio.TaskGroup` when using eager tasks that abort the task group too early.