io-async: Use ReadReady/WriteReady type state to avoid generating await points
Nobody has claimed this yet.
Assessment
- Difficulty
- 5/5
- Estimated time
- Over a week
- Newbie friendliness
- 25/100
- Issue type
- Feature
- Clarity
- Needs clarification
- Activity status
- Stale
- Tech stack
- rust
- Domain
- api, embedded-iot
Research direction
Start by reviewing the io-async ReadReady and WriteReady traits and the embedded-io readiness behavior described in the issue. Resolve which readiness-result API the team wants before implementing it, then define the compatibility and test expectations that would demonstrate immediate reads and writes no longer introduce additional await points.
Written by the indexing model from the issue text.
Description
A large concern moving from nb to async is that async reads lock the destination buffer across an await point: doing a let mut buf = [0; 128]; let len = await data.read(&mut buf)?; process(&buf[..len]).more().await(); turns the buf into a part of the future state machine, whereas a manually constructed state machine branch match self { AwaitRead => { let mut buf = [0; 128]; let len = data.read(&mut buf)?; DoMore(process(&buf[..len])) }} can do with a short-lived stack allocation.
The ReadReady/WriteReady traits work well in their way for the (blocking or immediate) embedded-io crate, but the state machine building process of the -async variant could use more guarantees. After ReadReady came back successfully, there will be an opportunity to read data immediately (or an error), but even if all sorts of link-time optimization were factored in into constructing the states (AIU they are not), the compiler can not see eg. through an OS's state that promises (under penalty of internal panics) that something is available immediately.
As mitigation, I suggest altering the ReadReady trait (and WriteReady analogously) such that calls can be made like this:
let ready_reader = reader.read_ready().await?;
let mut buf = [0; 128];
let len = ready_reader.read(&mut buf); // No await here!
let processed = process(&buf[..len]);
// drop(buf) happens here implicitly
more(processed).await?;
It could be implemented roughly like this:
enum ReadReadyResult<'r, R: Read> {
Ready(&'r mut R)
Error(R::Error)
}
impl<'r, R: Read> ReadReadyResult<'r, R> {
// really just a convenience thing; all actual work happens in the Read
#[inline]
fn read(self, buf: &mut [u8]) -> Result<usize, R::Error> {
match self {
Ready(r) => R::read_after_ready(self, buf),
Err(e) => Err(e),
}
}
}
trait Read {
async fn read_ready(&mut self) -> ReadReadyResult<'_, Self>;
fn read_after_ready(rrr: ReadReadyResult<'_, Self>, buf: &mut [u8]) -> Result<usize, Self::Error>;
// maybe this could even be provided: that might require turning the reference into a pinned reference so
// it can be polled once with an assertion that it is Ready. The async read_ready would probably manage
// to get such a pin.
}
Note that I think it might be a good idea to just make ReadReady async-return only returns the boolean/type readiness (ie. we'd have a struct ReadIsReady(R) instead of the enum ReadReadyResult) and then waits for the actual read to flush out any error – but then, I don't know why it was implemented that way in the first place.
If the team prefers to discuss this over a PR-style implementation, I can write it out into it (but my guess is that this benefits more from a preliminary discussion over jumping right in).
- Dominant language
- Rust
- Stars
- 2.7k
- Forks
- 282
- PR merge metrics
- No merged PRs in 30d
Contributor guide
No contributing guide indexed for this repository
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
More from rust-embedded/embedded-hal
-
Difficulty 2/5 1-3 hours Newbie friendliness 75/100
rust-embedded/embedded-hal#742 · 1 comment ·
-
Difficulty 5/5 Over a week Newbie friendliness 30/100
rust-embedded/embedded-hal#747 · 5 comments · 1 reaction ·
-
Difficulty 5/5 Over a week Newbie friendliness 35/100
rust-embedded/embedded-hal#746 · 2 comments · 1 reaction ·
-
Difficulty 4/5 3-5 days Newbie friendliness 48/100
rust-embedded/embedded-hal#745 · 1 reaction ·
-
Difficulty 2/5 1-3 hours Newbie friendliness 55/100
rust-embedded/embedded-hal#744 · 1 comment ·
All issues in rust-embedded/embedded-hal
Similar issues
-
Difficulty 2/5 1-3 hours Newbie friendliness 86/100
kwakseongjae/auto-hwp#319 ·
-
area:cli bug filter-quality good first issue priority:medium
Difficulty 2/5 1-3 hours Newbie friendliness 84/100
-
Difficulty 1/5 Under an hour Newbie friendliness 72/100
bevyengine/bevy#25861 ·
-
comp-datalake
Difficulty 2/5 1-3 hours Newbie friendliness 88/100
ClickHouse/ClickHouse#121222 ·
-
enhancement remote
Difficulty 2/5 1-3 hours Newbie friendliness 68/100