8000 gh-115957: Close coroutine if TaskGroup.create_task() raises an error… · python/cpython@ce0ae1d · GitHub
[go: up one dir, main page]

Skip to content
Sign in

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit ce0ae1d

Browse files
authored
gh-115957: Close coroutine if TaskGroup.create_task() raises an error (#116009)
1 parent 7114cf2 commit ce0ae1d

File tree

5 files changed

+42
-16
lines changed

5 files changed

+42
-16
lines changed

Doc/library/asyncio-task.rst

+7
Original file line numberDiff line numberDiff line change
@@ -334,6 +334,13 @@ and reliable way to wait for all tasks in the group to finish.
334334

335335
Create a task in this task group.
336336
The signature matches that of :func:`asyncio.create_task`.
337+
If the task group is inactive (e.g. not yet entered,
338+
already finished, or in the process of shutting down),
339+
we will close the given ``coro``.
340+
341+
.. versionchanged:: 3.13
342+
343+
Close the given coroutine if the task group is not active.
337344

338345
Example::
339346

Doc/whatsnew/3.13.rst

+6
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,12 @@ Other Language Changes
185185

186186
(Contributed by Sebastian Pipping in :gh:`115623`.)
187187

188+
* When :func:`asyncio.TaskGroup.create_task` is called on an inactive
189+
:class:`asyncio.TaskGroup`, the given coroutine will be closed (which
190+
prevents a :exc:`RuntimeWarning` about the given coroutine being
191+
never awaited).
192+
193+
(Contributed by Arthur Tacca and Jason Zhang in :gh:`115957`.)
188194

189195
New Modules
190196
===========

Lib/asyncio/taskgroups.py

+3
Original file line numberDiff line numberDiff line change
@@ -154,10 +154,13 @@ def create_task(self, coro, *, name=None, context=None):
154154
Similar to `asyncio.create_task`.
155155
"""
156156
if not self._entered:
157+
coro.close()
157158
raise RuntimeError(f"TaskGroup {self!r} has not been entered")
158159
if self._exiting and not self._tasks:
160+
coro.close()
159161
raise RuntimeError(f"TaskGroup {self!r} is finished")
160162
if self._aborting:
163+
coro.close()
161164
raise RuntimeError(f"TaskGroup {self!r} is shutting down")
162165
if context is None:
163166
task = self._loop.create_task(coro, name=name)

Lib/test/test_asyncio/test_taskgroups.py

+25-16
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import contextlib
88
from asyncio import taskgroups
99
import unittest
10+
import warnings
1011

1112
from test.test_asyncio.utils import await_without_task
1213

@@ -738,10 +739,7 @@ async def coro2(g):
738739
await asyncio.sleep(1)
739740
except asyncio.CancelledError:
740741
with self.assertRaises(RuntimeError):
741-
g.create_task(c1 := coro1())
742-
# We still have to await c1 to avoid a warning
743-
with self.assertRaises(ZeroDivisionError):
744-
await c1
742+
g.create_task(coro1())
745743

746744
with self.assertRaises(ExceptionGroup) as cm:
747745
async with taskgroups.TaskGroup() as g:
@@ -797,22 +795,25 @@ async def test_taskgroup_double_enter(self):
797795
pass
798796

799797
async def test_taskgroup_finished(self):
800-
tg = taskgroups.TaskGroup()
801-
async with tg:
802-
pass
803-
coro = asyncio.sleep(0)
804-
with self.assertRaisesRegex(RuntimeError, "is finished"):
805-
tg.create_task(coro)
806-
# We still have to await coro to avoid a warning
807-
await coro
798+
async def create_task_after_tg_finish():
799+
tg = taskgroups.TaskGroup()
800+
async with tg:
801+
pass
802+
coro = asyncio.sleep(0)
803+
with self.assertRaisesRegex(RuntimeError, "is finished"):
804+
tg.create_task(coro)
805+
806+
# Make sure the coroutine was closed when submitted to the inactive tg
807+
# (if not closed, a RuntimeWarning should have been raised)
808+
with warnings.catch_warnings(record=True) as w:
809+
await create_task_after_tg_finish()
810+
self.assertEqual(len(w), 0)
808811

809812
async def test_taskgroup_not_entered(self):
810813
tg = taskgroups.TaskGroup()
811814
coro = asyncio.sleep(0)
812815
with self.assertRaisesRegex(RuntimeError, "has not been entered"):
813816
tg.create_task(coro)
814-
# We still have to await coro to avoid a warning
815-
await coro
816817

817818
async def test_taskgroup_without_parent_task(self):
818819
tg = taskgroups.TaskGroup()
@@ -821,8 +822,16 @@ async def test_taskgroup_without_parent_task(self):
821822
coro = asyncio.sleep(0)
822823
with self.assertRaisesRegex(RuntimeError, "has not been entered"):
823824
tg.create_task(coro)
824-
# We still have to await coro to avoid a warning
825-
await coro
825+
826+
def test_coro_closed_when_tg_closed(self):
827+
async def run_coro_after_tg_closes():
828+
async with taskgroups.TaskGroup() as tg:
829+
pass
830+
coro = asyncio.sleep(0)
831+
with self.assertRaisesRegex(RuntimeError, "is finished"):
832+
tg.create_task(coro)
833+
loop = asyncio.get_event_loop()
834+
loop.run_until_complete(run_coro_after_tg_closes())
826835

827836

828837
if __name__ == "__main__":
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
When ``asyncio.TaskGroup.create_task`` is called on an inactive ``asyncio.TaskGroup``, the given coroutine will be closed (which prevents a ``RuntimeWarning``).

0 commit comments

Comments
 (0)
0