use std::sync::{Condvar, Mutex, MutexGuard, PoisonError};
use std::time::{Duration, Instant};
pub(crate) const POLL_CAP: Duration = Duration::from_millis(50);
pub(crate) const INITIAL_BACKOFF: Duration = Duration::from_millis(1);
pub(crate) fn next_backoff(current: Duration) -> Duration {
(current * 2).min(Duration::from_millis(20))
}
#[derive(Debug)]
pub(crate) struct Expired;
pub(crate) struct Monitor<S> {
state: Mutex<S>,
cond: Condvar,
}
impl<S> Monitor<S> {
pub(crate) fn new(state: S) -> Self {
Self {
state: Mutex::new(state),
cond: Condvar::new(),
}
}
pub(crate) fn lock(&self) -> MutexGuard<'_, S> {
self.state.lock().unwrap_or_else(PoisonError::into_inner)
}
pub(crate) fn mutate<R>(&self, f: impl FnOnce(&mut S) -> R) -> R {
let value = {
let mut guard = self.lock();
f(&mut guard)
};
self.cond.notify_all();
value
}
pub(crate) fn wait_timeout<'a>(
&self,
guard: MutexGuard<'a, S>,
dur: Duration,
) -> MutexGuard<'a, S> {
self.cond
.wait_timeout(guard, dur)
.unwrap_or_else(PoisonError::into_inner)
.0
}
pub(crate) fn wait_until<T>(
&self,
deadline: Instant,
mut check: impl FnMut(&mut S) -> Option<T>,
) -> Result<T, Expired> {
let mut guard = self.lock();
loop {
if let Some(v) = check(&mut guard) {
return Ok(v);
}
let now = Instant::now();
if now >= deadline {
return Err(Expired);
}
let sleep = (deadline - now).min(POLL_CAP);
guard = self.wait_timeout(guard, sleep);
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::sync::Arc;
use std::thread;
#[test]
fn wait_until_wakes_on_notification() {
let monitor = Arc::new(Monitor::new(0u32));
let m2 = Arc::clone(&monitor);
let handle = thread::spawn(move || {
m2.mutate(|v| *v = 7);
});
let got = monitor
.wait_until(Instant::now() + Duration::from_secs(5), |v| {
(*v == 7).then_some(*v)
})
.ok();
handle.join().unwrap();
assert_eq!(got, Some(7));
}
#[test]
fn wait_until_expires_at_the_deadline() {
let monitor = Monitor::new(());
let start = Instant::now();
let res = monitor.wait_until(start + Duration::from_millis(60), |()| None::<()>);
assert!(res.is_err());
assert!(start.elapsed() >= Duration::from_millis(60));
}
#[test]
fn backoff_doubles_and_caps() {
let mut d = INITIAL_BACKOFF;
let mut seen = Vec::new();
for _ in 0..8 {
seen.push(d.as_millis());
d = next_backoff(d);
}
assert_eq!(seen, vec![1, 2, 4, 8, 16, 20, 20, 20]);
}
#[test]
fn lock_recovers_from_poison() {
let monitor = Arc::new(Monitor::new(41u32));
let m2 = Arc::clone(&monitor);
let _ = thread::spawn(move || {
let _guard = m2.lock();
panic!("poison the mutex");
})
.join();
let mut guard = monitor.lock();
*guard += 1;
assert_eq!(*guard, 42);
}
}