]> git.ipfire.org Git - thirdparty/Python/cpython.git/commitdiff
[3.13] gh-128588: gh-128550: remove eager tasks optimization that missed and introduc...
authorMiss Islington (bot) <31488909+miss-islington@users.noreply.github.com>
Mon, 20 Jan 2025 17:36:50 +0000 (18:36 +0100)
committerGitHub <noreply@github.com>
Mon, 20 Jan 2025 17:36:50 +0000 (17:36 +0000)
gh-128588: gh-128550: remove eager tasks optimization that missed and introduced incorrect cancellations (GH-129063)
(cherry picked from commit ed6934e71e55d398df8263f4697f58e4a3815f69)

Co-authored-by: Thomas Grainger <tagrain@gmail.com>
Co-authored-by: Kumar Aditya <kumaraditya@python.org>
Lib/asyncio/taskgroups.py
Lib/test/test_asyncio/test_taskgroups.py
Misc/NEWS.d/next/Library/2025-01-20-13-12-39.gh-issue-128550.AJ5TOL.rst [new file with mode: 0644]

index 8af199d6dcc41acd8dfa78b33ee53df45700ef7a..8fda6c8d55e16c8c54d4862d63fb298d01b73dba 100644 (file)
@@ -197,14 +197,12 @@ class TaskGroup:
         else:
             task = self._loop.create_task(coro, name=name, context=context)
 
-        # optimization: Immediately call the done callback if the task is
+        # Always schedule the done callback even if the task is
         # already done (e.g. if the coro was able to complete eagerly),
-        # and skip scheduling a done callback
-        if task.done():
-            self._on_task_done(task)
-        else:
-            self._tasks.add(task)
-            task.add_done_callback(self._on_task_done)
+        # otherwise if the task completes with an exception then it will cancel
+        # the current task too early. gh-128550, gh-128588
+        self._tasks.add(task)
+        task.add_done_callback(self._on_task_done)
         try:
             return task
         finally:
index e52061aac923cc953bf880bc2bd7869cdb7151de..ad61cb46c7c07c38296b5f7b85ffa9f1056ded9d 100644 (file)
@@ -1032,6 +1032,56 @@ class BaseTestTaskGroup:
         self.assertListEqual(gc.get_referrers(exc), [])
 
 
+    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)
+
+
+    async def test_cancellation_does_not_leak_out_of_tg(self):
+        class MyError(Exception):
+            pass
+
+        async def throw_error():
+            raise MyError
+
+        try:
+            async with asyncio.TaskGroup() as tg:
+                tg.create_task(throw_error())
+        except* MyError:
+            pass
+        else:
+            self.fail("should have raised one MyError in group")
+
+        # if this test fails this current task will be cancelled
+        # outside the task group and inside unittest internals
+        # we yield to the event loop with sleep(0) so that
+        # cancellation happens here and error is more understandable
+        await asyncio.sleep(0)
+
+
 class TestTaskGroup(BaseTestTaskGroup, unittest.IsolatedAsyncioTestCase):
     loop_factory = asyncio.EventLoop
 
diff --git a/Misc/NEWS.d/next/Library/2025-01-20-13-12-39.gh-issue-128550.AJ5TOL.rst b/Misc/NEWS.d/next/Library/2025-01-20-13-12-39.gh-issue-128550.AJ5TOL.rst
new file mode 100644 (file)
index 0000000..f59feac
--- /dev/null
@@ -0,0 +1 @@
+Removed an incorrect optimization relating to eager tasks in :class:`asyncio.TaskGroup` that resulted in cancellations being missed.