0
0
mirror of https://github.com/python/cpython.git synced 2024-11-29 00:56:12 +01:00

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

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@langa.pl>
This commit is contained in:
Guido van Rossum 2022-06-30 10:10:46 -07:00 committed by GitHub
parent 4261b6bffc
commit 594c369949
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 27 additions and 4 deletions

View File

@ -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.

View File

@ -138,6 +138,8 @@ class TaskGroup:
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:

View File

@ -122,10 +122,8 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
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 @@ class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
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()

View File

@ -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.