use std::time::Duration;
pub const READY_TIMEOUT: Duration = match cfg!(windows) {
true => Duration::from_secs(15),
false => Duration::from_secs(5),
};
const FIRST_DELAY: Duration = Duration::from_millis(2);
const MAX_DELAY: Duration = Duration::from_millis(50);
pub async fn poll_until(done: &mut dyn FnMut() -> bool) -> bool {
let deadline = tokio::time::Instant::now() + READY_TIMEOUT;
let mut delay = FIRST_DELAY;
while !done() {
if tokio::time::Instant::now() >= deadline {
return false;
}
tokio::time::sleep(delay).await;
delay = (delay * 2).min(MAX_DELAY);
}
true
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
#[tokio::test(start_paused = true)]
async fn an_already_true_predicate_returns_without_sleeping() {
let started = tokio::time::Instant::now();
assert!(poll_until(&mut || true).await);
assert_eq!(tokio::time::Instant::now(), started, "it slept anyway");
}
#[tokio::test(start_paused = true)]
async fn a_predicate_that_never_flips_gives_up_at_the_timeout() {
let started = tokio::time::Instant::now();
assert!(!poll_until(&mut || false).await);
assert!(
tokio::time::Instant::now() - started >= READY_TIMEOUT,
"gave up early"
);
}
#[tokio::test(start_paused = true)]
async fn it_returns_as_soon_as_the_predicate_flips() {
let calls = Cell::new(0);
assert!(
poll_until(&mut || {
calls.set(calls.get() + 1);
calls.get() == 4
})
.await
);
assert_eq!(calls.get(), 4, "it kept polling after the flip");
}
#[tokio::test(start_paused = true)]
async fn the_delay_doubles_and_then_holds_at_the_ceiling() {
let gaps = std::cell::RefCell::new(Vec::new());
let last = Cell::new(tokio::time::Instant::now());
let calls = Cell::new(0);
poll_until(&mut || {
let now = tokio::time::Instant::now();
gaps.borrow_mut().push(now - last.get());
last.set(now);
calls.set(calls.get() + 1);
calls.get() == 8
})
.await;
let gaps = gaps.borrow();
assert_eq!(gaps[0], Duration::ZERO);
assert_eq!(gaps[1], FIRST_DELAY);
assert_eq!(gaps[2], FIRST_DELAY * 2);
assert_eq!(gaps[3], FIRST_DELAY * 4);
assert_eq!(gaps[4], FIRST_DELAY * 8);
assert_eq!(gaps[5], FIRST_DELAY * 16);
assert_eq!(gaps[6], MAX_DELAY);
assert_eq!(gaps[7], MAX_DELAY);
}
#[test]
fn windows_gets_a_longer_readiness_window_than_unix() {
let expected = 5 + 10 * u64::from(cfg!(windows));
assert_eq!(READY_TIMEOUT.as_secs(), expected);
assert!(READY_TIMEOUT > MAX_DELAY * 10);
}
}