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 9fa772ca9d02cc3..02a84b01a9e6fba 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 c47bf4ec9ed64ba..5a73afe842c138b 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 02a84b01a9e6fba..2fcdac54235b461 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 5a73afe842c138b..a53335ab0c4761c 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 000000000000000..7dbe6019a58982b --- /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 a53335ab0c4761c..28c79aa9e64cffd 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 28c79aa9e64cffd..e907bf11e304f81 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 7dbe6019a58982b..a9087f48a1356a0 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.