Uh oh!
There was an error while loading. Please reload this page.
gh-91887: Store strong references to pending tasks - #121264
Conversation
ZeroIntensity
left a comment
There was a problem hiding this comment.
Very nice PR! Just a few nitpicks.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
| def __schedule_callbacks(self): | ||
| """Internal: Ask the event loop to call all callbacks. | ||
| def _finish_execution(self): |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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:
deftest_strong_task_references(self):
called=Falseasyncdefcoro():
nonlocalcalledcalled=Trueasyncdefmain():
asyncio.create_task(coro())
loop=asyncio.new_event_loop()
try:
loop.run_until_complete(main())
finally:
loop.close()
self.assertTrue(called)Co-authored-by: Peter Bierma <zintensitydev@gmail.com>
1st1
commented
Sep 12, 2024
I can ponder on this during the core sprint (and think how this will play with uvloop). |
freakboy3742
commented
Sep 27, 2024
@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. |
9c5073b to
eb89d4cComparealexhartl
commented
Sep 27, 2024
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. |
Store strong references into a global set as well. Hopefully can get removed one day, as python/cpython#121264 was merged just this week :)
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 |
1st1
commented
Oct 9, 2024
@alexhartl have you considered just making |
gvanrossum
commented
Oct 9, 2024
@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.) |
1st1
commented
Oct 9, 2024
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 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. |
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.
Yes, true.
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. |
1st1
commented
Oct 11, 2024
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.
What are those modifications? Link?
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.
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: Also I find adding 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 |
alexhartl
commented
Dec 1, 2024
Sure. Are there other opinions? Otherwise, I can create a PR on this suggestion. |
This PR is stale because it has been open for 30 days with no activity. |
This adds a
_pending_tasksset toBaseEventLoop. OnTaskcreation, 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_tasksin_unregister_task. See the discussion at #91887.