Skip to content
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

Enable async operation #63

Merged
merged 25 commits into from
Jan 26, 2024
Merged

Enable async operation #63

merged 25 commits into from
Jan 26, 2024

Conversation

chrysn
Copy link
Member

@chrysn chrysn commented Oct 19, 2023

This sketch PR adds async building blocks -- wrappers around RIOT APIs that produce futures and can be .awaited.

It does not add an executor (the branch async-experiments-1 contains one, but it's not very good). The embassy-executor-riot module in the riot-module-examples contains an implementation of an embassy based executor in less than 50 lines of code.

  • A ZTimer sleep.
  • An implementation of the embedded-nal-async UDP traits based on the async socket API.

This suffices for simple examples, in particular any UDP echo tests. I'd love to add more APIs, but many are tricky:

  • TCP (through embedded-nal-async), UART (through embedded-io-async) and GPIO (through embedded-hal-async) should be straightforward enough (but are also not super exciting).

  • Mutex and message (including msg_bus, which is currently the only way to be notified of IP address changes) are tricky, as for neither, you can get a callback in the originating context. As callbacks are how wakers are triggered, implementing them will need cooperation from the executor. (This is in contrast to what is implemented now: There, the embassy-executor-riot knows nothing of what is implemented on top). The fundamental issues are:

    • A thread can only wait for one mutex at a time. An executor may have different tasks that wait for different mutexes. (Similarly, an application may find itself in a situation where it needs to wait for either of two mutexes).

      As long as mutexes are only used like lightweight critical sections (preventing simultaneous access to any given data structure, eg. while inserting into a linked list), that's all not too bad: For those, even an async environment may just block on them. (Maybe the Rust lint for holding a mutex across await points is even user configurable and could apply to our Mutex locks). However, our use of mutexes in RIOT is mixed (for example, SUIT has a worker lock that's held for full a full firmware update).

    • Messages are queued or even delivered only on wait. Even if a task could make the executor wait for its requested message when it is idle, unlike a thread a task can't guarantee that its executor is always idle when the task is idle -- so tasks will practically need queues more often than not. But they're accessible only in the sequence they come in (except responses to sent messages -- sending a message and getting the response might be straightforward to implement async'ly). So when one task is awaiting to process messages of type A, and task B has generally ordered messages of type B (but is currently doing some stuff inbetween, like working off the last message), then when a new message of the type that B ordered arrives, followed by one for A, the executor either needs to drop the one for B (in the model of B not having a queue) or needs to delay what is there for A.

      We might introduce some per-task queues where the executor dispatches messages as soon as they arrive (and that can probably be done without changes to RIOT), but this feels a lot like just reimplementing mboxes.

    (Which also means mboxes are probably on the easy list; gotta look through what can be used with mboxes these days.)

@chrysn chrysn requested a review from kaspar030 October 19, 2023 19:24
@chrysn
Copy link
Member Author

chrysn commented Oct 19, 2023

As for interesting mbox user applications: Seems that TCP is using it internally, and gnrc_netapi_mbox is available (but then again, that also has a callback style way of using it). CAN is using it, but I have no hardware to play with that.

@chrysn
Copy link
Member Author

chrysn commented Oct 21, 2023

gcoap client-side also goes onto the list of easy targets for async support.

@chrysn
Copy link
Member Author

chrysn commented Dec 5, 2023

Main merged in once more to get updated tests.

Two things are missing yet:

  • Tests
  • Runnability on riotdocker (doesn't trigger as long as there are no tests, but some of the bindings depend on 1.75 features which will be stable 2023-12-28)

@chrysn chrysn force-pushed the async-experiments-2 branch from 1b903bf to ab262b6 Compare December 6, 2023 11:59
@chrysn chrysn force-pushed the async-experiments-2 branch from ab262b6 to d517415 Compare January 26, 2024 13:32
@chrysn chrysn marked this pull request as ready for review January 26, 2024 13:49
@chrysn
Copy link
Member Author

chrysn commented Jan 26, 2024

The two stoppers are addressed: There is now an async ztimer test, and thanks to embassy updates, the tests can run on the stable image.

APIs don't need to be complete for this to go in, so next plan is to merge.

@chrysn chrysn merged commit 05835b3 into main Jan 26, 2024
59 checks passed
@chrysn chrysn deleted the async-experiments-2 branch January 26, 2024 18:20
@chrysn chrysn mentioned this pull request Aug 23, 2024
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant