GV

GeezerVison

The World Wide Web, restored to 800 pixels of glory.

Home|About|Gaming cabinet|Archives|How it works|Starter sites

On this site

  • Sign in
  • ActionsCI/CD and automation
  • CodespacesCloud development environments
  • All Features
  • Documentation
  • Training
  • Certifications
  • Build with Copilot
  • Customize Copilot
  • Integrations
  • Trending repos
  • GitHub Sponsors
  • Developer articles
  • Enterprises

This view

  • Original page
  • New address

GeezerVison is showing github.com — gh-128552: fix refcycles in eager task creation by graingert · Pull Request #128553 · python/cpython

gh-128552: fix refcycles in eager task creation #128553

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Sign up for GitHub

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
kumaraditya303 merged 13 commits into python:main from graingert:fix-taskgroup-eager-refcycle
Jan 7, 2025

Uh oh!

There was an error while loading. Please reload this page.

Merged

gh-128552: fix refcycles in eager task creation #128553

Show file tree
Hide file tree
Changes from 5 commits
Commits
Show all changes
13 commits
Select commit Hold shift + click to select a range
c82f30b
gh-128552: fix refcycles in eager task creation
graingert Jan 6, 2025
8a4eb56
Update Lib/asyncio/base_events.py
graingert Jan 6, 2025
35a38e4
Update Lib/asyncio/taskgroups.py
graingert Jan 6, 2025
afd3053
📜🤖 Added by blurb_it.
blurb-it[bot] Jan 6, 2025
9700ebf
Rename 2025-01-06-18-41-08.gh-issue-gh-128552.fV-f8j.rst to 2025-01-0…
graingert Jan 6, 2025
fe83add
Update Misc/NEWS.d/next/Library/2025-01-06-18-41-08.gh-issue-128552.f…
graingert Jan 6, 2025
a1f76df
Merge branch 'main' into fix-taskgroup-eager-refcycle
graingert Jan 6, 2025
7c966d0
add a version of the test with weakrefs
graingert Jan 7, 2025
89362f5
Merge branch 'main' into fix-taskgroup-eager-refcycle
graingert Jan 7, 2025
cf5cfe3
simplify and comment
graingert Jan 7, 2025
18b6ea9
Update Lib/asyncio/base_events.py
graingert Jan 7, 2025
ca71935
restore _set_event_loop_policy(None)
graingert Jan 7, 2025
cc39717
remove type annotations
graingert Jan 7, 2025
File filter
Filter by extension

Clear filters
Conversations
Failed to load comments.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Jump to
Jump to file
Failed to load files.
Loading

Uh oh!

There was an error while loading. Please reload this page.

Diff view
Diff view

There are no files selected for viewing

28 changes: 16 additions & 12 deletions 28 Lib/asyncio/base_events.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -463,21 +463,25 @@ def create_task(self, coro, *, name=None, context=None):

Return a task object.
"""
self._check_closed()
if self._task_factory is None:
task = tasks.Task(coro, loop=self, name=name, context=context)
if task._source_traceback:
del task._source_traceback[-1]
else:
if context is None:
# Use legacy API if context is not needed
task = self._task_factory(self, coro)
task = None
try:
Comment thread
graingert marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

self._check_closed()
if self._task_factory is None:
task = tasks.Task(coro, loop=self, name=name, context=context)
if task._source_traceback:
del task._source_traceback[-1]
else:
task = self._task_factory(self, coro, context=context)
if context is None:
# Use legacy API if context is not needed
task = self._task_factory(self, coro)
else:
task = self._task_factory(self, coro, context=context)
Comment thread
ZeroIntensity marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.


task.set_name(name)
task.set_name(name)

return task
return task
finally:
del task

def set_task_factory(self, factory):
"""Set a task factory that will be used by loop.create_task().
Expand Down
32 changes: 18 additions & 14 deletions 32 Lib/asyncio/taskgroups.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -192,20 +192,24 @@ def create_task(self, coro, *, name=None, context=None):
if self._aborting:
coro.close()
raise RuntimeError(f"TaskGroup {self!r} is shutting down")
if context is None:
task = self._loop.create_task(coro, name=name)
else:
task = self._loop.create_task(coro, name=name, context=context)

# optimization: Immediately call the done callback 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)
return task
task = None
try:
Comment thread
graingert marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

if context is None:
Comment thread
ZeroIntensity marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

task = self._loop.create_task(coro, name=name)
else:
task = self._loop.create_task(coro, name=name, context=context)

# optimization: Immediately call the done callback if the task is
Comment thread
graingert marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

# 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)
return task
finally:
del task

# Since Python 3.8 Tasks propagate all exceptions correctly,
# except for KeyboardInterrupt and SystemExit which are
Expand Down
24 changes: 15 additions & 9 deletions 24 Lib/test/test_asyncio/test_taskgroups.py
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@

from test.test_asyncio.utils import await_without_task

# To prevent a warning "test altered the execution environment"
Comment thread

@kumaraditya303 kumaraditya303 Jan 7, 2025

Copy link
Copy Markdown
Contributor

There was a problem hiding this comment.

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I know this isn't needed as you have changed it to use asyncio.EventLoop but let's keep it for now, it is easy to make a mistake on altering policy and is a pain to fix in CI if it happens without this.

Sorry, something went wrong.

Uh oh!

There was an error while loading. Please reload this page.

All reactions
def tearDownModule():
asyncio._set_event_loop_policy(None)


class MyExc(Exception):
Comment thread
graingert marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

pass

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


class TestTaskGroup(unittest.IsolatedAsyncioTestCase):
class BaseTestTaskGroup:

async def test_taskgroup_01(self):

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

def test_coro_closed_when_tg_closed(self):
async def test_coro_closed_when_tg_closed(self):
async def run_coro_after_tg_closes():
async with taskgroups.TaskGroup() as tg:
pass
coro = asyncio.sleep(0)
with self.assertRaisesRegex(RuntimeError, "is finished"):
tg.create_task(coro)
loop = asyncio.get_event_loop()
loop.run_until_complete(run_coro_after_tg_closes())

await run_coro_after_tg_closes()

async def test_cancelling_level_preserved(self):
async def raise_after(t, e):
Expand Down Expand Up @@ -998,5 +993,16 @@ class MyKeyboardInterrupt(KeyboardInterrupt):
self.assertListEqual(gc.get_referrers(exc), no_other_refs())


class TestTaskGroup(BaseTestTaskGroup, unittest.IsolatedAsyncioTestCase):
loop_factory = asyncio.EventLoop

class TestEagerTaskTaskGroup(BaseTestTaskGroup, unittest.IsolatedAsyncioTestCase):
@staticmethod
def loop_factory():
loop = asyncio.EventLoop()
loop.set_task_factory(asyncio.eager_task_factory)
return loop


if __name__ == "__main__":
unittest.main()
1 change: 1 addition & 0 deletions 1 Misc/NEWS.d/next/Library/2025-01-06-18-41-08.gh-issue-128552.fV-f8j.rst
Show comments
View file
Open in desktop
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Fix cyclic garbage introduced by :meth:`loop.create_task` and :meth:`asyncio.TaskGroup.create_task` holding a reference to the created task.
Comment thread
graingert marked this conversation as resolved.
Resolved

Uh oh!

There was an error while loading. Please reload this page.

Add this suggestion to a batch that can be applied as a single commit. This suggestion is invalid because no changes were made to the code. Suggestions cannot be applied while the pull request is closed. Suggestions cannot be applied while viewing a subset of changes. Only one suggestion per line can be applied in a batch. Add this suggestion to a batch that can be applied as a single commit. Applying suggestions on deleted lines is not supported. You must change the existing code in this line in order to create a valid suggestion. Outdated suggestions cannot be applied. This suggestion has been applied or marked resolved. Suggestions cannot be applied from pending reviews. Suggestions cannot be applied on multi-line comments. Suggestions cannot be applied while the pull request is queued to merge. Suggestion cannot be applied right now. Please check back later.
You are visitor 01402520 — GeezerVison does not store the pages you look up.
Best viewed with Netscape Navigator 4.7 or Internet Explorer 6 at 800×600. No cookies required.