Timeouts and Sleep
std::thread::sleep parks the whole thread. Inside an async runtime that is a disaster: one task naps and every other task sharing that worker thread stops making progress.
tokio::time::sleep is the async version. It yields, the runtime goes off and polls everything else, and your task is woken when the timer fires.
use tokio::time::sleep;
use std::time::Duration;
sleep(Duration::from_millis(50)).await;Putting a limit on a future
tokio::time::timeout wraps any future and races it against a timer.
use tokio::time::timeout;
let limit = Duration::from_millis(100);
match timeout(limit, slow_work()).await {
Ok(value) => println!("got {value}"),
Err(_) => println!("gave up"),
}The error type, Elapsed, carries nothing beyond "the time was up", so .ok() is usually all you want from it.
The important part is what happens to the inner future when the timer wins: it is dropped. Not signalled, not asked to stop. Dropped, part-finished, at whatever .await it was suspended on. Anything after that point never runs. This is what people mean when they say async Rust has cancellation built in.
Your Task
Implement with_timeout
pub async fn with_timeout<F>(
limit: Duration,
work: F,
) -> Option<F::Output>
where
F: Future,Run work with a deadline. Return Some(output) if it finished in time, None if the limit ran out first.
Implement retry
pub async fn retry(
attempts: usize,
gap: Duration,
op: impl FnMut() -> bool,
) -> Option<usize>Call op until it returns true, at most attempts times, waiting gap between one call and the next. Return the zero-based number of the attempt that succeeded, or None if none did.
The gap goes between attempts only. One attempt sleeps not at all, four failing attempts sleep three times.
let mut tries = 0;
let found = retry(5, gap, || {
tries += 1;
tries == 3
})
.await;
assert_eq!(found, Some(2));Notes
opneeds to bemutto be called, since it isFnMut.attemptsof0meansopis never called at all.- Keep every duration small. The test runner has a hard timeout.
Hints
Click here to reveal hints
timeout(limit, work).await.ok()is the whole body ofwith_timeout.- Take the closure as
mut opin the parameter list. - Loop over
0..attemptsand sleep at the top of the body whenever the index is not0. That is easier to get right than sleeping at the bottom. - Return from inside the loop the moment
op()is true, so no further attempt happens. - Fall through to
Noneafter the loop.
Run to execute your code and see test results here.