Crate aselect

Crate aselect 

Source
Expand description

§aselect

Wait on multiple branches, without canceling or starving any futures, while allowing safe access to mutable state. Works in #[no_std], allocates no memory, and has no non-optional dependencies. Tested with miri.

§Background

This crate implements aselect, a safer alternative to the tokio select!-macro. By using aselect, it becomes possible to avoid cancelling futures during normal operations, eliminating a class of bugs. See the excellent RFD 400 from Oxide for a great overview of cancellation safety in rust: https://rfd.shared.oxide.computer/rfd/400 . aselect also avoids the “FutureLock” class of bugs, described (also by Oxide) at https://rfd.shared.oxide.computer/rfd/0609, because it doesn’t allow async code in handlers (only in the actual concurrent arms).

§Comparison with tokio::select

The regular select! macro from tokio is very useful, but it has two properties that can be error-prone when said macro is used in a loop:

  • As soon as one select arm completes, all other arms are canceled. Many futures are not cancellation safe (e.g. tokio::sync::mpsc::Sender::send).
  • When an arm has completed, while the handler is executing, other arms are no longer polled.

In contrast to select!, aselect has these differences:

  • It implements futures::Stream, meaning it can be polled multiple times. When polled repeatedly, it never implicitly cancels any futures; arms are polled until they become ready. It also implements core::future::Future.
  • When polled, it always polls all active arms.
  • It has a different syntax (that allows it to be formatted by rustfmt).
  • It allows safe sharing of mutable state between select arms

§When to use tokio::select

Use tokio::select when:

  • Cancellation of futures is a desired outcome
  • Completion of one select arm means the continued execution of other arms is meaningless

§When to use aselect

Use aselect when:

  • You’re processing multiple async sources of information in a loop
  • When you need to run multiple futures to completion, while sharing state between them

§Tips

  • Both the setup and handler blocks return Option. This means the ? operator can be used in them.
  • Use the core::pin::pin!-macro to pin the aselect! expression when using it as a futures::Stream.
  • All handlers must return the same type. If no output is desired, they can just return () (which is the default). Otherwise they must return Option<Output<T>>. Note, since the handler is wrapped in Some, making sure the handler evaluates to Output is enough. Or you can explicitly return Some(Output::Value(x)).

§Implementation

aselect works by creating a set of structs that implement a state machine. Each select arm is its own struct, and consists of two closures and a stored future. One of the closures creates the future, and the other decides if the result of a future should cause aselect itself to produce a value.

aselect does not allocate memory on the heap.

§Features

Enable the futures feature (enabled by default) for Stream support. All aselect! invocations implement Stream (in addition to Future) when this feature is enabled.

Modules§

patterns
Async application main loops

Macros§

aselect
Evaluate multiple different async operations concurrently.

Enums§

Output
Output from a aselect handler.

Traits§

SafeResult
Trait that must be implemented by the type returned by the handler expression of a aselect arm.
Stream
A stream of values produced asynchronously.