use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::time::Duration;
const SLICE: Duration = Duration::from_millis(250);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Wake {
Poll,
Stop,
}
pub fn wait(interval: Duration, stop: &Arc<AtomicBool>, mut wake: impl FnMut() -> bool) -> Wake {
let mut waited = Duration::ZERO;
while waited < interval {
if stop.load(Ordering::Relaxed) {
return Wake::Stop;
}
std::thread::sleep(SLICE);
waited += SLICE;
if wake() {
break;
}
}
if stop.load(Ordering::Relaxed) {
Wake::Stop
} else {
Wake::Poll
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::atomic::AtomicUsize;
#[test]
fn a_stop_set_before_the_wait_returns_without_sleeping() {
let stop = Arc::new(AtomicBool::new(true));
let started = std::time::Instant::now();
let outcome = wait(Duration::from_hours(1), &stop, || false);
assert_eq!(outcome, Wake::Stop);
assert!(
started.elapsed() < Duration::from_secs(1),
"quitting must not wait out the interval"
);
}
#[test]
fn a_wake_request_ends_the_wait_early_and_asks_for_a_poll() {
let stop = Arc::new(AtomicBool::new(false));
let started = std::time::Instant::now();
let outcome = wait(Duration::from_hours(1), &stop, || true);
assert_eq!(outcome, Wake::Poll);
assert!(started.elapsed() < Duration::from_secs(1));
}
#[test]
fn a_zero_interval_polls_immediately_without_consulting_the_wake_flag() {
let stop = Arc::new(AtomicBool::new(false));
let asked = AtomicUsize::new(0);
let outcome = wait(Duration::ZERO, &stop, || {
asked.fetch_add(1, Ordering::Relaxed);
false
});
assert_eq!(outcome, Wake::Poll);
assert_eq!(asked.load(Ordering::Relaxed), 0);
}
#[test]
fn the_interval_is_honoured_when_nothing_interrupts_it() {
let stop = Arc::new(AtomicBool::new(false));
let started = std::time::Instant::now();
let outcome = wait(SLICE * 2, &stop, || false);
assert_eq!(outcome, Wake::Poll);
assert!(
started.elapsed() >= SLICE * 2,
"returned after {:?}, before the interval was up",
started.elapsed()
);
}
}