crate::ix!();
use parking_lot::{Condvar, Mutex};
use std::{
sync::atomic::{AtomicBool, Ordering},
time::{Duration as StdDuration, Instant},
};
#[derive(Default)]
pub struct ThreadInterrupt {
cond: Condvar,
gate: Mutex<()>,
flag: AtomicBool,
}
impl ThreadInterrupt {
#[inline]
pub fn as_bool(&self) -> bool {
self.flag.load(Ordering::Acquire)
}
pub fn new() -> Self {
trace!("ThreadInterrupt::new");
Self {
cond: Condvar::new(),
gate: Mutex::new(()),
flag: AtomicBool::new(false),
}
}
pub fn reset(&self) {
trace!("ThreadInterrupt::reset");
self.flag.store(false, Ordering::Release);
}
pub fn invoke(&self) {
trace!("ThreadInterrupt::invoke");
{
let _guard = self.gate.lock();
self.flag.store(true, Ordering::Release);
}
self.cond.notify_all();
}
pub fn sleep_for(&self, rel_time: StdDuration) -> bool {
debug!(
"ThreadInterrupt::sleep_for — requested {:?}",
rel_time
);
if self.as_bool() {
debug!("ThreadInterrupt::sleep_for — already interrupted");
return false;
}
let deadline = Instant::now() + rel_time;
let mut guard = self.gate.lock();
loop {
if self.as_bool() {
return false; }
let now = Instant::now();
if now >= deadline {
return true; }
let remaining = deadline - now;
if self
.cond
.wait_for(&mut guard, remaining)
.timed_out()
{
return true; }
}
}
}
#[cfg(test)]
mod thread_interrupt_timing_tests {
use super::*;
use std::sync::Arc;
use std::thread;
use std::time::{Duration as StdDuration, Instant};
#[traced_test]
fn uninterrupted_sleep_times_out() {
let ti = ThreadInterrupt::new();
let start = Instant::now();
let timed_out = ti.sleep_for(StdDuration::from_millis(150));
let elapsed = start.elapsed();
assert!(timed_out, "sleep_for must return true on timeout");
assert!(
elapsed >= StdDuration::from_millis(140),
"elapsed {:?} unexpectedly short",
elapsed
);
}
#[traced_test]
fn interrupt_wakes_early() {
let ti = Arc::new(ThreadInterrupt::new());
let sleeper = Arc::clone(&ti);
let handle = thread::spawn(move || sleeper.sleep_for(StdDuration::from_secs(10)));
thread::sleep(StdDuration::from_millis(50));
ti.invoke();
let timed_out = handle.join().expect("sleeping thread panicked");
assert!(!timed_out, "sleep_for should return false after interrupt");
}
}