Conversation
5a46a80 to
b5723ea
Compare
|
Wow, thanks for this! It's very nice to see that it can be implemented in pure Python. I don't know anything about TaskGroup so will need to learn how it works. |
You're right. That CI check will need fixing so it allows "Revert" commit messages. |
Actually the CI check is working OK, it's only supposed to check the commit messages that are unique to the PR. If you rebase (and force push) on latest master it will hopefully get the CI green. |
|
Pushed. Writing tests, found a significant bug (related to cancellation of course). Investigating. |
1a0dffd to
7d20532
Compare
|
Huh. I have no idea where those test failures are coming from. |
|
The CI logs say error is: It looks like the bytecode emitter allows 2 args but the native emitter only allows 1. |
Well, yeah, I saw that line, but what has that got to do with implementing |
|
Assuming you are using the unix port, to reproduce locally, try There are a number of tests that are disabled when emit=native because of I suspect you have written some Python code similar to one of these that causes the same error. |
|
I'm running this on a Pyboard 1.1 - TaskGroups are a very nice feature. Something Google seems loath to divulge is how to access return values from the members of a group (assuming they terminate normally). |
|
@peterhinch Glad that my code is of use. I'll try to fix up the CI errors shortly; battling with Covid aftereffects, so if somebody else wants to fix up this PR, feel free. You need to return any values explicitly, e.g. by setting an object attribute to it or queuing the value or whatever. |
|
Sorry you're unwell - I hope you feel better soon.
Is this still outstanding or is the code ready for review? I very much hope this is implemented: in many applications it's a big improvement over |
No, that's done. The workaround I implemented admittedly isn't particularly clean, but it gets the job done and doesn't alter non-taskgroup code (as that might introduce bugs or slowdowns). |
|
@dpgeorge @jimmo This is to advocate for Consider a communication link between two peers using an unreliable medium such as WiFi. This may be near the limit of range and the link can fail in a variety of ways. The simplest way to recover is the "belt and braces" approach: if a peer encounters an unrecoverable error, it takes the link down for a period long enough for the other peer also to suffer an unrecoverable error. That way both peers start from a "power up" state. A
If any of these experience an error that cannot be handled locally, the exception is passed up to be handled by the task that created the group. When this occurs, every task in the group terminates in an orderly way, running cleanup code in The |
|
@peterhinch Exactly. Unstructured tasks (like asyncio's baseline tasks) mean that you have to keep track of what's still running and what might have to be cancelled and/or restarted on your own. Code doing that tends to contain a heap of bugs you can't test for, and basically doesn't scale. In contrast, when a taskgroup's async context manager has ended you know that there's no dangling tasks, unprocessed The latter point is much more powerful than you'd assume at first glance. |
This patch adds a mostly-compatible backport of CPython 3.11's `TaskGroup` class to uasyncio. Also, there is a new `run_server` method in uasyncio/stream.py which supports task groups and doesn't run in the background (no need). TODO: Write some more tests. Closes micropython#8508. Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
The C implementation (_uasyncio.Task) was not. Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
Tasks can be cancelled before they have an opportunity to run. Handling this situation is non-trivial in MicroPython's context. Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
If two tasks try to start sleeping at the same time, the SingletonGenerator will be in use, thus we need to allocate a new temporary handler on the heap. As the singleton isn't that single any more, it gets renamed. Found by taskgroup test micropython#12. Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
This fixes some edge cases with cancellation. Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
still failing, though Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
Rudimentary but works. Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
If there's an exception (or more) *and* a cancellation in a taskgroup, the cancellation must be ignored. Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
This mirrors Python change 594c369. Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
Cancelling a taskgroup that's no longer active should not do anything. Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
`run_server` is not in CPython. We like to avoid that. Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
Signed-off-by: Matthias Urlichs <matthias@urlichs.de>
|
I'm sorry but this keeps getting bumped to the next release. I need to set aside some quality time to understand task groups and review this PR. |
|
A patch that adds |
projectgus
left a comment
There was a problem hiding this comment.
Hi @smurfix,
Sorry I'm coming in very late to this! Thanks for keeping the PR open and up to date for so long.
I have some comments but they're mostly docs and CPython differences. The implementation looks very clean and well commented, and the test coverage is comprehensive which is great.
Apologies if anything I've commented on here has previously discussed, I skimmed the comment history but I haven't read every comment - sorry.
Certainly I'm not asking for any major additional work, don't want perfect to become the enemy of good here as this looks really useful to have in MicroPython! Are you still interested in working on it?
|
|
||
| class TaskGroup | ||
| --------------- | ||
|
|
There was a problem hiding this comment.
At the moment the link at the top of this page is to CPython 3.8's asyncio reference, which doesn't have TaskGroups. Suggest starting this section with a short explanation of this being a port of the CPython 3.11 feature, and a link to https://docs.python.org/3.11/library/asyncio-task.html#task-groups
| See Nathaniel J. Smith's `essay on Structured Concurrency | ||
| <https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/>`_ | ||
| for an introduction why you should use taskgroups instead of starting | ||
| "naked" tasks. | ||
|
|
||
| .. note:: | ||
| His "nursery" objects are called "taskgroup" in asyncio; the | ||
| equivalent of a "go statement" is `Loop.create_task`. |
There was a problem hiding this comment.
This is interesting context and motivation, but I'm a bit torn on whether this is suitable for the MicroPython asyncio reference or not. (i.e. the equivalent CPython docs provide a single sentence summary of what TaskGroups do and that feels more appropriate for reference documentation.)
| This object is an async context managed holding a group of tasks. | ||
| Tasks can be added to the group using `TaskGroup.create_task`. |
There was a problem hiding this comment.
| This object is an async context managed holding a group of tasks. | |
| Tasks can be added to the group using `TaskGroup.create_task`. | |
| An asynchronous context manager holding a group of tasks. Tasks can be | |
| added to the group using `TaskGroup.create_task`. All tasks are awaited when | |
| the context manager exits. |
(Originally this was just a suggestion to fix the typo, but changed it to copying the CPython doc summary exactly!)
|
|
||
| This is a coroutine. | ||
|
|
||
| .. function:: run_server(callback, host, port, backlog=5, taskgroup=None) |
There was a problem hiding this comment.
I have two concerns here:
- This function doesn't exist in CPython 3.11 asyncio, and we're trying not to add any more MicroPython-only names in CPython-compatible modules..
- This function doesn't appear to exist in MicroPython asyncio module either, at least in this version of the PR it's in the
examples/run_server.pyfile. Ergonomically this is a bit rough for someone who wants to use it.
Is the comment above about how start_server doesn't work well with TaskGroups exclusive to MicroPython? If so, is there any way that we can change MicroPython's implementation of start_server to be closer to CPython's in this regard?
(Otherwise, if start_server also doesn't work well with TaskGroups on CPython then a good path forward might be to add run_server as a micropython-lib library that can be separately mip install-ed, and mention it in the docs for start_server here.)
|
|
||
| Create a task from the given *coro* and return the new `Task` object. | ||
|
|
||
| You should not call this function when you're using taskgroups. |
There was a problem hiding this comment.
| You should not call this function when you're using taskgroups. | |
| .. warning: Due to MicroPython internal limitations, this function can't be used at the same time as TaskGroups. |
| from .taskgroup import TaskGroup | ||
|
|
||
| __version__ = (3, 0, 0) | ||
| __version__ = (3, 0, 1) |
There was a problem hiding this comment.
| __version__ = (3, 0, 1) | |
| __version__ = (3, 1, 0) |
Nitpicky, but adding a whole new feature is at least worth a minor version bump!
| @@ -0,0 +1,841 @@ | |||
| # Test Lock class | |||
There was a problem hiding this comment.
| # Test Lock class | |
| # Test TaskGroups |
| If multiple subtasks raise exceptions in parallel, it's unclear which | ||
| of them should be propagated. Thus an `ExceptionGroup` exception | ||
| collects them and is raised instead. | ||
|
|
There was a problem hiding this comment.
| .. note:: In CPython ExceptionGroup is a built-in exception, but in MicroPython it is | |
| part of the asyncio module. | |
| MicroPython does not support CPython 3.11's ``except*`` syntax for handling | |
| exception groups automatically. |
| MicroPython does not support CPython 3.11's syntax for filtering handling | ||
| exception groups. | ||
|
|
There was a problem hiding this comment.
| MicroPython does not support CPython 3.11's syntax for filtering handling | |
| exception groups. |
Suggest removing this from here and covering it in the class documentation above.
There was a problem hiding this comment.
Looks like some of these tests can run on CPython (as the top of the file checks if ExceptionGroup is a built-in), but not all of them (hence the .exp file and these checks)?
Not asking you to necessarily change anything, but how much work would it be to have tests which run on CPython & MicroPython instead?

This backports 3.11's TaskGroup class to uasyncio.
I don't care much about the
except *stuff, not in µPy context anyway, but sane error recovery is crucial and taskgroups make this job a whole lot easier, not to mention much less bug prone. (Writing from a lot of Trio and anyio experience here.)Bottom line: I refuse to write async code without using taskgroups. So here you are.
TODO: Write a couple of tests.Closes #8508.