rust-embedded / linux-embedded-hal

Implementation of the `embedded-hal` traits for Linux devices
Apache License 2.0
236 stars 40 forks source link

Async-tokio DelayNs implementation inaccurate for delays shorter than a few ms #111

Open eldruin opened 8 months ago

eldruin commented 8 months ago

hey fyi tokio::time::sleep is only good down to ms resolutions, for more granularity we typically need something nanosleep or a hot loop, so this implementation won't be accurate for anything less than a couple of ms.

having had to solve this a couple of times i suspect a more accurate way to do this would involve using epoll_wait with a timeout for > 1us delays (more accurate kernel timing than sleep is, particularly under the tokio runtime) and a hot loop over Instant::elapsed() for > 1ns delays (check elapsed and hit the waker every round).

possibly combining both into something like:


let now = Instant::now()
loop {
// Grab elapsed at the start of the loop
let elapsed = now.elapsed();

// Break once we exceed the delay duration if elapsed > duration { break; }

// Calculate the remaining sleep time let remainder = duration - elapsed;

// epoll or spin depending on remainder if remainder > Duration::from_millis(1) { epoll_sleep().await; } else { spin_sleep().await; } }


>
> some folks do [more complex things](https://crates.io/crates/spin_sleep) to balance accuracy and cpu use, but, this is fairly straightforward and would be closer to operating as intended.
(it's also good to keep track of elapsed times because there are _reasons_ a sleep might end early, which has caused problems for me in the past)

_Originally posted by @ryankurte in https://github.com/rust-embedded/linux-embedded-hal/issues/109#issuecomment-1913732707_