Skip to content

Commit c82f30b

Browse files
committed
pythongh-128552: fix refcycles in eager task creation
1 parent f826bec commit c82f30b

3 files changed

Lines changed: 47 additions & 35 deletions

File tree

‎Lib/asyncio/base_events.py‎

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -463,21 +463,24 @@ def create_task(self, coro, *, name=None, context=None):
463463
464464
Return a task object.
465465
"""
466-
self._check_closed()
467-
if self._task_factory is None:
468-
task = tasks.Task(coro, loop=self, name=name, context=context)
469-
if task._source_traceback:
470-
del task._source_traceback[-1]
471-
else:
472-
if context is None:
473-
# Use legacy API if context is not needed
474-
task = self._task_factory(self, coro)
466+
try:
467+
self._check_closed()
468+
if self._task_factory is None:
469+
task = tasks.Task(coro, loop=self, name=name, context=context)
470+
if task._source_traceback:
471+
del task._source_traceback[-1]
475472
else:
476-
task = self._task_factory(self, coro, context=context)
473+
if context is None:
474+
# Use legacy API if context is not needed
475+
task = self._task_factory(self, coro)
476+
else:
477+
task = self._task_factory(self, coro, context=context)
477478

478-
task.set_name(name)
479+
task.set_name(name)
479480

480-
return task
481+
return task
482+
finally:
483+
del task
481484

482485
def set_task_factory(self, factory):
483486
"""Set a task factory that will be used by loop.create_task().

‎Lib/asyncio/taskgroups.py‎

Lines changed: 17 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -192,20 +192,23 @@ def create_task(self, coro, *, name=None, context=None):
192192
if self._aborting:
193193
coro.close()
194194
raise RuntimeError(f"TaskGroup {self!r} is shutting down")
195-
if context is None:
196-
task = self._loop.create_task(coro, name=name)
197-
else:
198-
task = self._loop.create_task(coro, name=name, context=context)
199-
200-
# optimization: Immediately call the done callback if the task is
201-
# already done (e.g. if the coro was able to complete eagerly),
202-
# and skip scheduling a done callback
203-
if task.done():
204-
self._on_task_done(task)
205-
else:
206-
self._tasks.add(task)
207-
task.add_done_callback(self._on_task_done)
208-
return task
195+
try:
196+
if context is None:
197+
task = self._loop.create_task(coro, name=name)
198+
else:
199+
task = self._loop.create_task(coro, name=name, context=context)
200+
201+
# optimization: Immediately call the done callback if the task is
202+
# already done (e.g. if the coro was able to complete eagerly),
203+
# and skip scheduling a done callback
204+
if task.done():
205+
self._on_task_done(task)
206+
else:
207+
self._tasks.add(task)
208+
task.add_done_callback(self._on_task_done)
209+
return task
210+
finally:
211+
del task
209212

210213
# Since Python 3.8 Tasks propagate all exceptions correctly,
211214
# except for KeyboardInterrupt and SystemExit which are

‎Lib/test/test_asyncio/test_taskgroups.py‎

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,6 @@
1212

1313
from test.test_asyncio.utils import await_without_task
1414

15-
# To prevent a warning "test altered the execution environment"
16-
def tearDownModule():
17-
asyncio._set_event_loop_policy(None)
18-
19-
2015
class MyExc(Exception):
2116
pass
2217

@@ -38,7 +33,7 @@ def no_other_refs():
3833
return [coro]
3934

4035

41-
class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
36+
class BaseTestTaskGroup:
4237

4338
async def test_taskgroup_01(self):
4439

@@ -832,15 +827,15 @@ async def test_taskgroup_without_parent_task(self):
832827
with self.assertRaisesRegex(RuntimeError, "has not been entered"):
833828
tg.create_task(coro)
834829

835-
def test_coro_closed_when_tg_closed(self):
830+
async def test_coro_closed_when_tg_closed(self):
836831
async def run_coro_after_tg_closes():
837832
async with taskgroups.TaskGroup() as tg:
838833
pass
839834
coro = asyncio.sleep(0)
840835
with self.assertRaisesRegex(RuntimeError, "is finished"):
841836
tg.create_task(coro)
842-
loop = asyncio.get_event_loop()
843-
loop.run_until_complete(run_coro_after_tg_closes())
837+
838+
await run_coro_after_tg_closes()
844839

845840
async def test_cancelling_level_preserved(self):
846841
async def raise_after(t, e):
@@ -998,5 +993,16 @@ class MyKeyboardInterrupt(KeyboardInterrupt):
998993
self.assertListEqual(gc.get_referrers(exc), no_other_refs())
999994

1000995

996+
class TestTaskGroup(BaseTestTaskGroup, unittest.IsolatedAsyncioTestCase):
997+
loop_factory = asyncio.EventLoop
998+
999+
class TestEagerTaskTaskGroup(BaseTestTaskGroup, unittest.IsolatedAsyncioTestCase):
1000+
@staticmethod
1001+
def loop_factory():
1002+
loop = asyncio.EventLoop()
1003+
loop.set_task_factory(asyncio.eager_task_factory)
1004+
return loop
1005+
1006+
10011007
if __name__ == "__main__":
10021008
unittest.main()

0 commit comments

Comments
 (0)