[Python-checkins] GH-94398: TaskGroup: Fail create_task() during shutdown (GH-94400) (GH-94463)

ambv webhook-mailer at python.org
Thu Jun 30 14:02:47 EDT 2022


https://github.com/python/cpython/commit/7fe949e5eac3efa8bb0cd9e7274380a044a408ea
commit: 7fe949e5eac3efa8bb0cd9e7274380a044a408ea
branch: 3.11
author: Miss Islington (bot) <31488909+miss-islington at users.noreply.github.com>
committer: ambv <lukasz at langa.pl>
date: 2022-06-30T20:02:42+02:00
summary:

GH-94398: TaskGroup: Fail create_task() during shutdown (GH-94400) (GH-94463)

Once the task group is shutting down, it should not be possible to create a new task.
Here "shutting down" means `self._aborting` is set, indicating that at least one task
has failed and we have cancelled all others.

Co-authored-by: Łukasz Langa <lukasz at langa.pl>
(cherry picked from commit 594c3699492bfb007650538726d953cbed55de04)

Co-authored-by: Guido van Rossum <guido at python.org>

files:
A Misc/NEWS.d/next/Library/2022-06-29-04-42-56.gh-issue-94398.YOq_bJ.rst
M Doc/library/asyncio-task.rst
M Lib/asyncio/taskgroups.py
M Lib/test/test_asyncio/test_taskgroups.py

diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst
index 9b76548433b6a..76969762dd9f5 100644
--- a/Doc/library/asyncio-task.rst
+++ b/Doc/library/asyncio-task.rst
@@ -320,6 +320,7 @@ no new tasks may be added to the group.
 The first time any of the tasks belonging to the group fails
 with an exception other than :exc:`asyncio.CancelledError`,
 the remaining tasks in the group are cancelled.
+No further tasks can then be added to the group.
 At this point, if the body of the ``async with`` statement is still active
 (i.e., :meth:`~object.__aexit__` hasn't been called yet),
 the task directly containing the ``async with`` statement is also cancelled.
diff --git a/Lib/asyncio/taskgroups.py b/Lib/asyncio/taskgroups.py
index 9e0610deed281..3ca65062efd27 100644
--- a/Lib/asyncio/taskgroups.py
+++ b/Lib/asyncio/taskgroups.py
@@ -138,6 +138,8 @@ def create_task(self, coro, *, name=None, context=None):
             raise RuntimeError(f"TaskGroup {self!r} has not been entered")
         if self._exiting and not self._tasks:
             raise RuntimeError(f"TaskGroup {self!r} is finished")
+        if self._aborting:
+            raise RuntimeError(f"TaskGroup {self!r} is shutting down")
         if context is None:
             task = self._loop.create_task(coro)
         else:
diff --git a/Lib/test/test_asyncio/test_taskgroups.py b/Lib/test/test_asyncio/test_taskgroups.py
index 69369a6100a8f..26fb5e466af92 100644
--- a/Lib/test/test_asyncio/test_taskgroups.py
+++ b/Lib/test/test_asyncio/test_taskgroups.py
@@ -122,10 +122,8 @@ async def runner():
         self.assertTrue(t2.cancelled())
 
     async def test_cancel_children_on_child_error(self):
-        """
-        When a child task raises an error, the rest of the children
-        are cancelled and the errors are gathered into an EG.
-        """
+        # When a child task raises an error, the rest of the children
+        # are cancelled and the errors are gathered into an EG.
 
         NUM = 0
         t2_cancel = False
@@ -722,6 +720,27 @@ async def coro(val):
             await t2
             self.assertEqual(2, ctx.get(cvar))
 
+    async def test_taskgroup_no_create_task_after_failure(self):
+        async def coro1():
+            await asyncio.sleep(0.001)
+            1 / 0
+        async def coro2(g):
+            try:
+                await asyncio.sleep(1)
+            except asyncio.CancelledError:
+                with self.assertRaises(RuntimeError):
+                    g.create_task(c1 := coro1())
+                # We still have to await c1 to avoid a warning
+                with self.assertRaises(ZeroDivisionError):
+                    await c1
+
+        with self.assertRaises(ExceptionGroup) as cm:
+            async with taskgroups.TaskGroup() as g:
+                g.create_task(coro1())
+                g.create_task(coro2(g))
+
+        self.assertEqual(get_error_types(cm.exception), {ZeroDivisionError})
+
 
 if __name__ == "__main__":
     unittest.main()
diff --git a/Misc/NEWS.d/next/Library/2022-06-29-04-42-56.gh-issue-94398.YOq_bJ.rst b/Misc/NEWS.d/next/Library/2022-06-29-04-42-56.gh-issue-94398.YOq_bJ.rst
new file mode 100644
index 0000000000000..c6e7e967d106e
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2022-06-29-04-42-56.gh-issue-94398.YOq_bJ.rst
@@ -0,0 +1 @@
+Once a :class:`asyncio.TaskGroup` has started shutting down (i.e., at least one task has failed and the task group has started cancelling the remaining tasks), it should not be possible to add new tasks to the task group.



More information about the Python-checkins mailing list