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-91887: Store strong references to pending tasks by alexhartl · Pull Request #121264 · python/cpython

Conversation

@alexhartl

@alexhartl alexhartl commented Jul 2, 2024 •
edited by bedevere-app Bot
Loading

Uh oh!

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

Copy link
Copy Markdown

This adds a _pending_tasks set to BaseEventLoop. On Task creation, a (strong) reference to the task is added to this set in _register_task. When a task completes, the respective reference is removed from _pending_tasks in _unregister_task. See the discussion at #91887.

  • Issue: asyncio: Use strong references for free-flying tasks #91887

Sorry, something went wrong.

Uh oh!

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

elenakrittik, 5j9, and Eclips4 reacted with thumbs up emoji
All reactions
  • 👍 3 reactions
alexhartl added 2 commits July 2, 2024 10:00
@alexhartl
Store strong references to pending tasks
c229526
@alexhartl
NEWS, ACKs
bbdbce1
@alexhartl
alexhartl requested review from 1st1, asvetlov, gvanrossum, kumaraditya303 and willingc as code owners July 2, 2024 12:46
@ghost

ghost commented Jul 2, 2024 •
edited by ghost
Loading

Uh oh!

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

Copy link
Copy Markdown

All commit authors signed the Contributor License Agreement.
CLA signed

All reactions

Sorry, something went wrong.

Uh oh!

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

@bedevere-app bedevere-app Bot mentioned this pull request Jul 2, 2024
asyncio: Use strong references for free-flying tasks #91887
Open
@bedevere-app bedevere-app Bot added the awaiting review label Jul 2, 2024
@kumaraditya303 kumaraditya303 added the DO-NOT-MERGE label Jul 3, 2024
ZeroIntensity
ZeroIntensity requested changes Jul 10, 2024
View reviewed changes

@ZeroIntensity ZeroIntensity left a comment

Copy link
Copy Markdown
Member

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.

Very nice PR! Just a few nitpicks.

Sorry, something went wrong.

Uh oh!

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

All reactions
Comment thread Misc/NEWS.d/next/Library/2024-07-02-14-07-32.gh-issue-91887.eWzc5E.rst Resolved

Uh oh!

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

Comment thread Misc/NEWS.d/next/Library/2024-07-02-14-07-32.gh-issue-91887.eWzc5E.rst Resolved

Uh oh!

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

Comment thread Lib/asyncio/futures.py

def __schedule_callbacks(self):
"""Internal: Ask the event loop to call all callbacks.
def _finish_execution(self):

@ZeroIntensity ZeroIntensity Jul 10, 2024

Copy link
Copy Markdown
Member

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.

For clarification, what's the reason for this name change? This is still, effectively, scheduling all callbacks -- "finish execution" is a bit more ambiguous to me.

Sorry, something went wrong.

Uh oh!

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

All reactions

@alexhartl alexhartl Jul 11, 2024

Copy link
Copy Markdown
Author

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.

Yes, in Future, "_schedule_callbacks" is perfectly fine to describe what the function does. When overriding this function in Task, I think _finish_execution is more meaningful to indicate that it will be called when the task completes.

Sorry, something went wrong.

Uh oh!

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

ZeroIntensity reacted with thumbs up emoji AronmasF reacted with heart emoji
All reactions
  • 👍 1 reaction
  • ❤️ 1 reaction
Comment thread Lib/test/test_asyncio/test_tasks.py

@ZeroIntensity ZeroIntensity Jul 10, 2024

Copy link
Copy Markdown
Member

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.

It might be a good idea to add a test to make sure that this actually fixes #91887, to make sure that someone doesn't accidentally break this in the future.

Likely would be something like:

def test_strong_task_references(self):
    called = False

    async def coro():
        nonlocal called
        called = True

    async def main():
        asyncio.create_task(coro())

    loop = asyncio.new_event_loop()
    try:
        loop.run_until_complete(main())
    finally:
        loop.close()
        self.assertTrue(called)

Sorry, something went wrong.

Uh oh!

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

All reactions
@bedevere-app bedevere-app Bot added awaiting core review and removed awaiting review labels Jul 10, 2024
alexhartl and others added 2 commits July 11, 2024 08:30
@alexhartl @ZeroIntensity
Improved news item
9a5ada0
Co-authored-by: Peter Bierma <[email protected]>
@alexhartl
Add test code to ensure python#91887 is fixed
5d2b57e
ZeroIntensity
ZeroIntensity approved these changes Jul 12, 2024
View reviewed changes

@ZeroIntensity ZeroIntensity left a comment

Copy link
Copy Markdown
Member

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.

LGTM

Sorry, something went wrong.

Uh oh!

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

All reactions
@1st1

1st1 commented Sep 12, 2024

Copy link
Copy Markdown
Member

I can ponder on this during the core sprint (and think how this will play with uvloop).

All reactions

Sorry, something went wrong.

Uh oh!

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

@hugovk hugovk added the sprint label Sep 12, 2024
@freakboy3742

freakboy3742 commented Sep 27, 2024

Copy link
Copy Markdown
Contributor

@alexhartl I'm at the CPython core team sprint, so I've taken the liberty of merging with main so I can discuss this with @1st1 and others. There were some conflicts introduced as a result of #120974.

All reactions

Sorry, something went wrong.

Uh oh!

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

@freakboy3742
Merge branch 'main' into strong-refs-for-bg-tasks
eb89d4c
@freakboy3742
freakboy3742 force-pushed the strong-refs-for-bg-tasks branch from 9c5073b to eb89d4c Compare September 27, 2024 16:54
@alexhartl

alexhartl commented Sep 27, 2024

Copy link
Copy Markdown
Author

Thank you for picking this up @freakboy3742 ! Yes, the last time I checked, the asyncio C code appeared to be in the middle of some restructuring. Let me know if I can help with anything.

All reactions

Sorry, something went wrong.

Uh oh!

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

paravoid added a commit to paravoid/ircstream that referenced this pull request Sep 30, 2024
@paravoid
Follow-up to the "weak references to tasks" heisenbug
b84ef60
Store strong references into a global set as well. Hopefully can get
removed one day, as python/cpython#121264 was
merged just this week :)
1st1
1st1 requested changes Oct 9, 2024
View reviewed changes

@1st1 1st1 left a comment •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Member

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'm blocking this PR from being merged to ponder on it (feel free to dismiss the block in a week). For one I'm really not sure I like the event loop to have new APIs, I think this is better be solved at asyncio level in an event loop independent way.

cc @ambv @pablogsal

Sorry, something went wrong.

Uh oh!

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

All reactions
@bedevere-app bedevere-app Bot removed the awaiting core review label Oct 9, 2024
@bedevere-app

bedevere-app Bot commented Oct 9, 2024

Copy link
Copy Markdown

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

All reactions

Sorry, something went wrong.

Uh oh!

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

@bedevere-app bedevere-app Bot added the awaiting changes label Oct 9, 2024
@1st1 1st1 mentioned this pull request Oct 9, 2024
gh-124309: fix staggered race on eager tasks #124847
Merged
@1st1

1st1 commented Oct 9, 2024

Copy link
Copy Markdown
Member

@alexhartl have you considered just making _scheduled_tasks a regular set instead of using weakref.WeakSet()? What's the downside of that? IMO it doesn't matter much where a strong reference is stored -- in the event loop or in the asyncio module. Either way a proper cleanup is required, but using a regular set would be a trivial change and everything in the ecosystem would just work.

All reactions

Sorry, something went wrong.

Uh oh!

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

@gvanrossum

gvanrossum commented Oct 9, 2024

Copy link
Copy Markdown
Member

@alexhartl have you considered just making _scheduled_tasks a regular set instead of using weakref.WeakSet()? What's the downside of that? IMO it doesn't matter much where a strong reference is stored -- in the event loop or in the asyncio module. Either way a proper cleanup is required, but using a regular set would be a trivial change and everything in the ecosystem would just work.

@1st1 But that would keep the awkward design where all loops share the global "all tasks" set, which is not how it's ever used. (I believe this design was just a historical accident; I've never found an explanation.)

All reactions

Sorry, something went wrong.

Uh oh!

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

@1st1

1st1 commented Oct 9, 2024

Copy link
Copy Markdown
Member

@1st1 But that would keep the awkward design where all loops share the global "all tasks" set, which is not how it's ever used. (I believe this design was just a historical accident; I've never found an explanation.)

Yeah, but what's awkward about it? There's a low level API that manages the all tasks set and other event loops (at least uvloop) already use that API. Adding yet another tracking API for running tasks is harder than switching the tracking API we already have to just use a regular set. The additional tracking will only introduce additional, albeit minuscule, overhead. At least this is how I see this. I do think it would be quite elegant to make the existing APIs asyncio._register_task and asyncio._unregister_task have some additional functionality.

Adding new API (like what this PR is doing) means that other loops will have to always implement it (or be broken). Which I obviously can do for uvloop, quite easily, but it will grow the API surface which is already huge.

Lastly, a minor point: event loop doesn't have a lot to do with tasks. The loop is mostly concerned with running callbacks. Task is a self-contained primitive that just schedules callbacks to the event loop. So it rubs me the wrong way to introduce tracking to the loop for Tasks, I believe a global threadlocal mapping is a better solution, which is what "all tasks" can be.

All reactions

Sorry, something went wrong.

Uh oh!

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

@alexhartl

alexhartl commented Oct 10, 2024 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Author

I'd prefer to avoid holding strong references in global scope to reduce the potential for memory leaks. I.e. the loop owns these references and we can be sure that everything is cleaned up at latest when the loop is destructed.

Also, the modifications that @kumaraditya303 did lately were a lot about performance improvements. Registering all tasks in a dict, and unregistering in a done callback is quite likely to negate these improvements to some extent, no matter where this dict is stored. Having this set and weakset separate might give us the opportunity to make this behaviour optional, so that the user is able to retain the performance improvements.

Adding new API (like what this PR is doing) means that other loops will have to always implement it (or be broken). Which I obviously can do for uvloop, quite easily, but it will grow the API surface which is already huge.

Yes, true.

Lastly, a minor point: event loop doesn't have a lot to do with tasks. The loop is mostly concerned with running callbacks. Task is a self-contained primitive that just schedules callbacks to the event loop. So it rubs me the wrong way to introduce tracking to the loop for Tasks, I believe a global threadlocal mapping is a better solution, which is what "all tasks" can be.

Technically, that is true. But from a user's perspective it's the loop that basically represents the state of asyncio. I think it would make a lot of sense if tasks are owned by this loop.

All reactions

Sorry, something went wrong.

Uh oh!

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

@1st1

1st1 commented Oct 11, 2024

Copy link
Copy Markdown
Member

I'd prefer to avoid holding strong references in global scope to reduce the potential for memory leaks. I.e. the loop owns these references and we can be sure that everything is cleaned up at latest when the loop is destructed.

IMO this isn't a strong argument. Actual applications rarely have more than one event loop during the whole program run (be it a short program or a web server). Now, if there's a bug not cleaning up tasks properly, then the bug is better to be actually fixed, otherwise the fact that event loop clears its tasks won't help at all.

Also, the modifications that @kumaraditya303 did lately were a lot about performance improvements.

What are those modifications? Link?

Registering all tasks in a dict, and unregistering in a done callback is quite likely to negate these improvements to some extent, no matter where this dict is stored.

This is such a minor micro-performance thing in a context of the relatively heavy cost of 'await' and the Task abstraction itself that IMO it's not worth talking about it. Adding/removing a Task from a dict will not be detectable even in a micro-benchmark, it will be below noise floor.

Technically, that is true. But from a user's perspective it's the loop that basically represents the state of asyncio. I think it would make a lot of sense if tasks are owned by this loop.

I see the logic here, but I still think that adding this API to the loop does not make sense in light of other APIs to track tasks that already exist in asyncio: _register_task(task), _enter_task(task).

Also I find adding _-leading APIs to the event loop inelegant.

So bottom line, I'm -1 on merging this PR as is. Please let's work together to re-align it and make it better fit with the existing asyncio APIs.


I suggest we keep in place the part of this PR that deterministically calls _unregister_task. _scheduled_tasks becomes a set(). That's it.

All reactions

Sorry, something went wrong.

Uh oh!

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

@ZeroIntensity ZeroIntensity mentioned this pull request Oct 20, 2024
Adding an internal C-API for dynamic arrays #125543
Closed
@jakkdl jakkdl mentioned this pull request Nov 17, 2024
New asyncio rule: directly passing coroutine to gather, shield, wait_for, wait, or as_completed python-trio/flake8-async#319
Open
@alexhartl

alexhartl commented Dec 1, 2024

Copy link
Copy Markdown
Author

Sure. Are there other opinions? Otherwise, I can create a PR on this suggestion.

5j9 reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

Sorry, something went wrong.

Uh oh!

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

@github-actions

github-actions Bot commented Apr 17, 2026

Copy link
Copy Markdown

This PR is stale because it has been open for 30 days with no activity.

All reactions

Sorry, something went wrong.

Uh oh!

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

@github-actions github-actions Bot added the stale Stale PR or inactive for long period of time. label Apr 17, 2026
@coderabbitai coderabbitai Bot mentioned this pull request May 9, 2026
Update from task 2da08ca6-2de5-4e1d-8f9f-37ab56ae6d68 Henxun/mvvm_pyside6#4
Merged
@ambv

ambv commented Oct 23, 2026

Copy link
Copy Markdown
Contributor

The problem this PR tries to solve still exists but the design of asyncio in Python changed enough that this will need a new approach. C tasks are now an intrusive per-thread linked list that optimistically removes tasks when finished.

There's good ideas here around deterministic unregister on completion, and ownership scoped so loop teardown releases tasks, but we'd have to implement them without new loop API.

All reactions

Sorry, something went wrong.

Uh oh!

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

@ambv ambv closed this Oct 23, 2026
@github-project-automation github-project-automation Bot moved this from In Progress to Done in Sprint Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@1st1 1st1 1st1 requested changes
@ZeroIntensity ZeroIntensity ZeroIntensity approved these changes
@asvetlov asvetlov Awaiting requested review from asvetlov asvetlov is a code owner
@gvanrossum gvanrossum Awaiting requested review from gvanrossum
@kumaraditya303 kumaraditya303 Awaiting requested review from kumaraditya303 kumaraditya303 is a code owner
@willingc willingc Awaiting requested review from willingc willingc is a code owner

Uh oh!

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

Assignees

No one assigned

Labels

awaiting changes DO-NOT-MERGE sprint stale Stale PR or inactive for long period of time.

Projects

Sprint
Archived in project

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

Uh oh!

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

8 participants

@alexhartl @1st1 @freakboy3742 @gvanrossum @ambv @ZeroIntensity @hugovk @kumaraditya303
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 01402503 — 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.