Uh oh!
There was an error while loading. Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork 34k
bpo-46752: Introduce task groups in asyncio#31270
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.
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
Uh oh!
There was an error while loading. Please reload this page.
Conversation
gvanrossum commented Feb 11, 2022 • edited by 1st1
Loading Uh oh!
There was an error while loading. Please reload this page.
edited by 1st1
Uh oh!
There was an error while loading. Please reload this page.
My plan is roughly the following: - [x] Copy the files from EdgeDb without modifications (named following asyncio conventions) - [ ] Bang on the tests until they will run - [ ] Bang on the tests until they pass - [ ] Remove pre-3.11 compatibility code - [ ] Switch from MultiError to ExceptionGroup - [ ] Other cleanup - [ ] Add a public API to tasks.py to replace `__cancel_requested__`
Two remaining issues: - [ ] _test_taskgroup_21 doesn't work (it didn't in EdgeDb either) - [ ] the test framework complains about a change to the event loop policy
This required some changes to the tests since EdgeDb's MultiError has some ergonomic conveniences that ExceptionGroup doesn't: - A helper method to get the types of the exceptions - It puts the number and types of the exceptions in the message Also, in one case (test_taskgroup_14) an EG nested inside another EG was raised, whereas the original code just raised one EG. This remains to be investigated.
iritkatriel commented Feb 11, 2022 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Add to todo list: give the tests meaningful names.
That's probably useful only for tests. We could move assertExceptionIsLike to a mixin in test.support.
Subclassing was an afterthought. We assumed it's not needed, now I think we're assuming it would be rare. If you do subclass here, make sure you define derive(). Otherwise split() will not create parts of your type.
We could, it might be too long then though. |
asvetlov commented Feb 11, 2022
Regarding If we have an agreement for it, I can prepare a pull request on the weekend. |
gvanrossum commented Feb 11, 2022
So it would, but I am worried about breaking code that depends on the current behavior -- often developers have no idea how to debug such a failure, and it would give 3.11 a bad rep "randomly breaks async apps". But maybe it's not so bad? Would any tests break?
Give it a whir and we can see if it's viable. If not, we can do something less drastic to just expose the cancel-requested state (maybe that's just |
gvanrossum commented Feb 11, 2022 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Done (i.e., added to the todo list :-).
Perhaps. We'll see how often this comes up. I find my current solution manageable, even though it isn't as concise as Yury's code.
So we have a real decision to make here: do we need
Yeah, we might have to truncate if there are too many. But I already know that I've been confused regularly when I did |
gvanrossum commented Feb 11, 2022
Mystery solved: I wrote a small test program and found that the full error message contains the nested So the |
gvanrossum commented Feb 11, 2022 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
FWIW I am strongly leaning towards not having a custom UPDATE: Done. |
gvanrossum commented Feb 12, 2022
I looked more into handling of This is the core of test 20: asyncdefcrash_soon(): awaitasyncio.sleep(0.1) 1/0asyncdefnested(): try: awaitasyncio.sleep(10) finally: raiseKeyboardInterruptasyncdefrunner(): asyncwithtaskgroups.TaskGroup() asg: g.create_task(crash_soon()) awaitnested()We expect the Test 21 is almost the same except the I did add new tests that parallel test 20 and 21 but use I think there are good enough reasons why asyncio has these special cases for |
gvanrossum commented Feb 12, 2022
I just chatted with some Trio folks and they had decent use cases for allowing this. I'm going to look into how easy it would be to add this functionality. @1st1 |
iritkatriel commented Feb 12, 2022 • edited by gvanrossum
Loading Uh oh!
There was an error while loading. Please reload this page.
edited by gvanrossum
Uh oh!
There was an error while loading. Please reload this page.
I made bpo46729 for this and a draft PR so we can see the impact on tracebacks in the tests. |
We stop when there are no unfinished tasks, and then no new tasks can be created. (TODO: more thorough testing of edge cases?)
gvanrossum commented Feb 12, 2022
Looked pretty easy, so I made this change. (However, as soon as cancel scopes came up they started saying asyncio was dumb so I gave up asking more about that.) |
agronholm commented Feb 12, 2022
I didn't say asyncio was dumb. I said the way cancellation works is. You noted this yourself here. My generalization was unjust and I apologize for that. |
agronholm commented Feb 12, 2022
To get back to the matter at hand: localized timeouts are a pattern I often see in the wild, and they're somewhat problematic without cancel scopes. Take this example: asyncwithtimeout(5): ...Such a construct is provided not only by AnyIO and trio, but asyncio_timeout as well. The problem arises when the task is cancelled as a whole and the timeout expires, both before the event loop has a chance to raise the cancellation exception in the task. How, then, do you know if you need to simply exit the |
gvanrossum commented Feb 12, 2022
@agronholm If you want to discuss cancel scopes please open a new bpo issue. |
bedevere-bot commented Feb 15, 2022
🤖 New build scheduled with the buildbot fleet by @gvanrossum for commit 9712241 🤖 If you want to schedule another build, you need to add the ":hammer: test-with-buildbots" label again. |
asvetlov commented Feb 15, 2022 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Agree. I personally don't see any blocker. |
gvanrossum commented Feb 15, 2022
Okay, let's merge once buildbots are green, then we can improve tests and docs in later PRs. BTW, are you interested in having something like your async-timeout in the stdlib as well? This might satisfy the Trio folks' desire for cancel scopes. (In order to work properly it would have to use .uncancel().) |
asvetlov commented Feb 15, 2022 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
Yes, sure. I think the current API is stable and consistent. The only known problem is a case when timeout occurs for a task that was cancelled on the previous loop iteration. upd the mentioned |
1st1 commented Feb 15, 2022
Andrew, I also think it's time for us to add a context manager for timeouts to asyncio. |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
1st1 left a comment • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
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.
@gvanrossum I think this is ready. With this merged we'll need to update the docs in a few places to de-prioritize asyncio.gather() and steer people towards TaskGroups.
Tinche commented Feb 16, 2022
Are you folks acquainted with https://pypi.org/project/quattro/, since there's talk of timeout context managers in the stdlib? |
Tinche commented Feb 16, 2022
It's also a little unfortunate Quattro task groups weren't used instead, they are identical but with type annotations (and I think I fixed a minor issue with something, pickling?) |
gvanrossum commented Feb 16, 2022
A simple "thank you" would have sufficed. |
1st1 commented Feb 16, 2022
I've taken a quick look, the file seems identical with what Guido committed (except typing). If there's something you fixed and you can remember what it was, please file a bug or create a PR. Nit: I've also noticed that you removed the original copyright comment from the file. Not that I care in this specific case, but you shouldn't remove copyright notices from files you borrow from other projects unless you're explicitly permitted to do so. (Thanks for crediting in README though!) |
agronholm commented Feb 16, 2022
Type annotations are still not a thing in the standard library. Were you hoping for the code to be type annotated? @Tinche On another note, I am currently writing a BPO for potential low level asyncio cancellation machinery updates to support the implementation of cancel scopes and level cancellation in asyncio. The writing process has largely turned into a rubber ducking session and led to new insights on my part. The interesting aspect of Quattro is that it implements cancel scopes along with task groups, which this PR does not. It would be interesting to compare our implementations privately. I'll reach out to you by Gitter so as not to unnecessarily flood this PR. |
Tinche commented Feb 16, 2022
Sorry, I restored it just now. To me it's just clutter since the repo already has a license included. I see the TaskGroup here doesn't have it, so I might switch to that then ;) |
gvanrossum commented Feb 16, 2022 • edited by bedevere-bot
Loading Uh oh!
There was an error while loading. Please reload this page.
edited by bedevere-bot
Uh oh!
There was an error while loading. Please reload this page.
1st1 commented Feb 16, 2022
You should include the full copyright comment. |
gvanrossum commented Feb 17, 2022 • edited
Loading Uh oh!
There was an error while loading. Please reload this page.
edited
Uh oh!
There was an error while loading. Please reload this page.
While this change was merged, it is still being debated, and we may revise this. See e.g. bpo-46771. (In general, what was merged is intended as a starting point for further experiments and discussion, at least until feature freeze / beta 1, late May.) |
This is EdgeDB's TaskGroup class, adapted for Python 3.11.
In the individual commits you can see how I evolved this from the version in EdgeDB.
Here's a to-do list:
test_taskgroup_14I get a nestedExceptionGroup, while the original got a flat MultiError.TaskGroupError? Implementing it correctly is awkward. We could just raiseBaseExceptionGroup.If we decide to keep it, we need to fix it to do the hack where it inherits fromBaseExceptioniff at least one of the exceptions does. We also need to overridederive().BaseException,.KeyboardInterruptandSystemExitMaketest_taskgroup_21work? (It wasn't working in EdgeDB either, so it was crudely disabled.)tasks.pyto replace__cancel_requested__, and get rid of the monkey-patching.__aexit__()is already waiting?.uncancel()call in__aexit__()(currently on line 97). Dropping that line does not cause any tests to fail.I also learned a few things about the ergonomics of
ExceptionGroup:BaseExceptionhacks are clever but awkward to replicate in a subclass.str()of an EG is rather sparse (Yury's version adds the number of sub-exceptions and their types).CC: @iritkatriel, @1st1
Co-authored-by: Yury Selivanov yury@edgedb.com
Co-authored-by: Andrew Svetlov andrew.svetlov@gmail.com
https://bugs.python.org/issue46752