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-97696: DRAFT asyncio eager tasks factory prototype by itamaro · Pull Request #101613 · python/cpython

Conversation

@itamaro

@itamaro itamaro commented Feb 6, 2023 •
edited
Loading

Uh oh!

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

Copy link
Copy Markdown
Contributor

based on GH-98137 + feedback left there

still much left to do here, but something is working, so wanted to get early feedback!

with an opt build and the checked-in benchmarking script:

baseline (no eagerness)

./python.exe async_tree.py -s no_suspension -p
3.12.0a4+ (heads/gather-early-return-dirty:a2a7caf48d, Feb  3 2023, 16:14:11) [Clang 12.0.0 (clang-1200.0.32.28)]
/Users/itamaro/work/pyexe/main-opt/python.exe
Scenario: no_suspension
Time: 1.1034402861259878 s
Tasks created: 55989
Suspense called: 0

eager is ~2x faster in this scenario:

./python.exe async_tree.py -s no_suspension -p -e
3.12.0a4+ (heads/gather-early-return-dirty:a2a7caf48d, Feb  3 2023, 16:14:11) [Clang 12.0.0 (clang-1200.0.32.28)]
/Users/itamaro/work/pyexe/main-opt/python.exe
Scenario: no_suspension
Time: 0.5100075129885226 s
Tasks created: 3
Suspense called: 0

TODOs remaining:

  • implement changes in C tasks too
  • make needed changes in taskgroups
  • handle task constructors and factories that don't support yield_result don't see a reason for this
  • fix existing tests
  • add new tests
  • remove benchmarking script
  • extend the async tree benchmark to cover taskgroups
  • update pyperformance to support eager factory, run more benchmarks
  • handle AsyncStopIteration?
  • news and docs

to discuss:

  1. do we want the task_factory arg I added to the runner? in this or a separate PR?
  2. we are still leaving perf on the table by creating tasks and linking them to a gathering future in gather even when all future already completed. maybe something similar applies to taskgroups too, I didn't look yet. we can handle the case that all futures are completed to return synchronously (in a followup PR). any reason not to do it?
  3. even more perf by reimplementing things in C (like gather)
  • Issue: Add eager task creation API to asyncio #97696

Sorry, something went wrong.

Uh oh!

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

All reactions
itamaro added 6 commits February 3, 2023 16:51
@itamaro
Copy pythonGH-98137, make some changes, add benchmarking script, tryi…
1ea845c
…ng to get it working
@itamaro
asyncio runner takes optional task factory arg
de6d910
@itamaro
don't coro.send if the event loop is not running (yet)
56610e7
@itamaro
modify async tree script to support eager factory
34082a7
@itamaro
don't over-count tasks that yield result immediately
ce5beaf
@itamaro
handle task._source_traceback in eager task factory
38d7b0b
@bedevere-bot bedevere-bot added the awaiting review label Feb 6, 2023
@bedevere-bot bedevere-bot mentioned this pull request Feb 6, 2023
Add eager task creation API to asyncio #97696
Closed
@arhadthedev arhadthedev added topic-asyncio stdlib Standard Library Python modules in the Lib/ directory labels Feb 6, 2023
@arhadthedev arhadthedev changed the title [GH-97696] DRAFT asyncio eager tasks factory prototype gh-97696: DRAFT asyncio eager tasks factory prototype Feb 6, 2023
itamaro added 5 commits February 10, 2023 13:47
@itamaro
stop checking for eager factory in taskgroups
6afbaab
@itamaro
refactor async tree benchmark to work with TaskGroup or gather depend…
a6e68bc
…ing on flags and feature availability
@itamaro
restore C task
464bd49
@itamaro
yield_result -> coro_result
ac26ad6
@itamaro
Support coro_result in Task C impl
ae2dcf3
@itamaro
itamaro force-pushed the asyncio-eager-tasks-playground branch from fa7c13e to ae2dcf3 Compare February 11, 2023 18:09
DinoV
DinoV reviewed Feb 14, 2023
View reviewed changes
Comment thread Lib/asyncio/runners.py
# Note: the class is final, it is not intended for inheritance.

def __init__(self, *, debug=None, loop_factory=None):
def __init__(self, *, debug=None, loop_factory=None, task_factory=None):

@DinoV DinoV Feb 14, 2023

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.

Do we really need a separate task_factory here? Ultimately this is just influencing the loop after it's created, couldn't that be rolled into the existing loop_factory mechanism?

Sorry, something went wrong.

Uh oh!

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

itamaro reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

@itamaro itamaro Feb 14, 2023

Copy link
Copy Markdown
Contributor 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.

no we don't need this. it was convenient to add it for my testing. sure, the same result can be achieved with the loop factory, but this feels cleaner, so I figured I'd suggest it and see what others think

Sorry, something went wrong.

Uh oh!

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

All reactions
DinoV
DinoV reviewed Feb 14, 2023
View reviewed changes
Comment thread Lib/asyncio/tasks.py Resolved

Uh oh!

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

DinoV
DinoV reviewed Feb 14, 2023
View reviewed changes
Comment thread Modules/_asynciomodule.c Outdated
}
else {
Py_INCREF(coro_result);
task_step2_impl(state, self, coro_result);

@DinoV DinoV Feb 14, 2023

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.

The return value should be checked here, I assume if it's NULL we'll want to error out. But there should also presumably be a test case added which will hit that.

Sorry, something went wrong.

Uh oh!

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

itamaro reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

@itamaro itamaro Feb 14, 2023

Copy link
Copy Markdown
Contributor 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.

I'd like to find the test case that would trip on this first :)

Sorry, something went wrong.

Uh oh!

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

All reactions
DinoV
DinoV reviewed Feb 14, 2023
View reviewed changes
Comment thread Modules/_asynciomodule.c Outdated
}
}
else {
Py_INCREF(coro_result);

@DinoV DinoV Feb 14, 2023

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.

This incref seems a little weird? The caller should be keeping this alive for the lifetime of the call, and if task_step2_impl wants to hold onto it then it seems it should do the inc ref.

Sorry, something went wrong.

Uh oh!

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

All reactions

@itamaro itamaro Feb 14, 2023

Copy link
Copy Markdown
Contributor 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.

I ran into refcnt assertions and adding this made them go away 😬 I don't have a stronger justification for doing it here, so I'll need to dig deeper

Sorry, something went wrong.

Uh oh!

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

All reactions

@carljm carljm Mar 2, 2023

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.

from reading the code, I think the issue is on line 2911 in task_step_handle_result_impl, where the result is handed off to the task and there's currently a comment that "no incref is necessary." That comment made sense when result was fully a local variable of task_step_impl, so it could just hand off its owned reference to the task. but now that code is wrong (or at least, is assuming task_step_handle_result_impl steals the reference to its result argument, which isn't the typical/best approach.) So I think adding this incref is actually one "correct" option (as in, the refcounts all end up correct), but it's probably not the clearest / most maintainable option. The better option might be to add the incref down in task_step_handle_result_impl where it gives a reference to the task, and then add a decref of result at the end of task_step_impl after calling task_step_handle_result_impl (since task_step_handle_result_impl will no longer be stealing that reference.)

Sorry, something went wrong.

Uh oh!

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

itamaro reacted with thumbs up emoji
All reactions
  • 👍 1 reaction

@itamaro itamaro Mar 7, 2023

Copy link
Copy Markdown
Contributor 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.

good point, thanks @carljm !

tried moving the incref where you suggested and still ran into negative refcounts (in different cases though, like "different loop" test). it works if I incref right in the beginning of the function - we can go with that, or I can try make it more granular by chasing down all the branches that would need the incref the result

Sorry, something went wrong.

Uh oh!

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

All reactions

@carljm carljm Mar 8, 2023

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.

So currently the code in task_step_handle_result_impl was written with the assumption that it owns its reference to result. I didn't initially look down through its code far enough to see all the places that assumption manifests, but in addition to line 2911, there's also line 3004, and then below that there are a bunch of error exit cases that do Py_DECREF(result), which only makes sense under the assumption that the reference is owned.

So the choices are either to incref it right away, so that the assumption made by the rest of the existing code continues to be true, or to modify the code so it increfs on line 2911 and 3004 (I think those are the only spots I see?) where it stores a new reference, and so it doesn't decref on all the various error exits at the end of the func.

The latter choice is more typical, because generally doing it more lazily results in fewer reference counting operations (i.e. in one of the error-exit cases, no refcount operations should be needed at all, but the "incref early" option means there's a pointless incref and then decref), and also because it's typically less error prone to future changes (instead of having to remember to decref result on every exit path that does nothing with result, which is harder to remember precisely because you aren't doing anything with result, you only have to incref on the paths where you actually do store a new reference to result, which is pretty natural. But it does mean more changes to the current code.

Sorry, something went wrong.

Uh oh!

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

itamaro reacted with heart emoji
All reactions
  • ❤️ 1 reaction

@itamaro itamaro Mar 10, 2023

Copy link
Copy Markdown
Contributor 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.

thanks for the explanation! super helpful :)
the last commit I just pushed (adding 2 increfs and removing a bunch of decrefs) seems to be working!

Sorry, something went wrong.

Uh oh!

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

carljm reacted with hooray emoji
All reactions
  • 🎉 1 reaction
itamaro added 9 commits February 22, 2023 09:33
@itamaro
Merge branch 'main' into asyncio-eager-tasks-playground
9794715
@itamaro
Address Dino's review comments
0b447b0
rename "step2", add TODOs to address before this is ready
@itamaro
passthrough coro_result from custom constructor only if it is set
f2748e2
@itamaro
add != NULL assertion on step2 result
61ac5d0
@itamaro
Merge branch 'main' into asyncio-eager-tasks-playground
cbd14fb
@itamaro
fix result refcnting in task_step_handle_result_impl
3503337
@itamaro
Add eager task factory tests
49c0e89
@itamaro
cleanup eager task factory tests
8a5229a
@itamaro
add news blurb
d1e5fd1
itamaro added 3 commits March 15, 2023 12:19
@itamaro
Merge branch 'main' into asyncio-eager-tasks-playground
580fb1f
@itamaro
apply patchcheck fixes in asyncio.tasks
6284c41
@itamaro
regenerate clinic
34123a7
@itamaro itamaro closed this Apr 23, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Reviewers

@carljm carljm carljm left review comments
@DinoV DinoV DinoV left review comments
@1st1 1st1 Awaiting requested review from 1st1 1st1 will be requested when the pull request is marked ready for review 1st1 is a code owner
@asvetlov asvetlov Awaiting requested review from asvetlov asvetlov will be requested when the pull request is marked ready for review asvetlov is a code owner
@kumaraditya303 kumaraditya303 Awaiting requested review from kumaraditya303 kumaraditya303 will be requested when the pull request is marked ready for review kumaraditya303 is a code owner
@willingc willingc Awaiting requested review from willingc willingc will be requested when the pull request is marked ready for review willingc is a code owner

Uh oh!

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

Assignees

No one assigned

Labels

awaiting review stdlib Standard Library Python modules in the Lib/ directory topic-asyncio

Projects

None yet

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.

5 participants

@itamaro @carljm @DinoV @arhadthedev @bedevere-bot
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.