#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Timeout {
polls: u32,
}
impl Timeout {
pub const DEFAULT: Self = Self::polls(10_000_000);
#[must_use]
pub const fn polls(polls: u32) -> Self {
Self { polls }
}
#[must_use]
pub const fn count(self) -> u32 {
self.polls
}
#[must_use]
pub const fn start(self) -> Deadline {
Deadline {
remaining: self.polls,
}
}
pub fn spin<T>(self, mut ready: impl FnMut() -> Option<T>) -> Result<T, TimedOut> {
let mut deadline = self.start();
loop {
if let Some(value) = ready() {
return Ok(value);
}
deadline.tick()?;
}
}
}
impl Default for Timeout {
fn default() -> Self {
Self::DEFAULT
}
}
#[derive(Debug)]
pub struct Deadline {
remaining: u32,
}
impl Deadline {
pub fn tick(&mut self) -> Result<(), TimedOut> {
match self.remaining.checked_sub(1) {
Some(rest) => {
self.remaining = rest;
Ok(())
}
None => Err(TimedOut),
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct TimedOut;
#[cfg(test)]
mod tests {
use super::{TimedOut, Timeout};
#[test]
fn zero_budget_expires_on_first_tick() {
assert_eq!(Timeout::polls(0).start().tick(), Err(TimedOut));
}
#[test]
fn budget_allows_exactly_n_ticks() {
let mut d = Timeout::polls(3).start();
assert!(d.tick().is_ok());
assert!(d.tick().is_ok());
assert!(d.tick().is_ok());
assert_eq!(d.tick(), Err(TimedOut));
}
#[test]
fn spin_checks_condition_before_spending() {
assert_eq!(Timeout::polls(0).spin(|| Some(7)), Ok(7));
}
#[test]
fn spin_gives_up_on_a_stuck_condition() {
let mut polls = 0u32;
let r: Result<(), _> = Timeout::polls(100).spin(|| {
polls += 1;
None
});
assert_eq!(r, Err(TimedOut));
assert_eq!(polls, 101);
}
}