use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use std::thread;
use std::time::{Duration, Instant};
use crate::error::{Error, Result};
pub(crate) const POLL: Duration = Duration::from_millis(50);
#[derive(Debug, Clone, Default)]
pub struct CancelToken {
cancelled: Arc<AtomicBool>,
}
impl CancelToken {
#[must_use]
pub fn new() -> Self {
Self::default()
}
pub fn cancel(&self) {
self.cancelled.store(true, Ordering::Release);
}
#[must_use]
pub fn is_cancelled(&self) -> bool {
self.cancelled.load(Ordering::Acquire)
}
pub(crate) fn check(&self) -> Result<()> {
if self.is_cancelled() {
Err(Error::Cancelled)
} else {
Ok(())
}
}
pub(crate) fn sleep(&self, duration: Duration) -> Result<()> {
let deadline = Instant::now() + duration;
loop {
self.check()?;
let left = deadline.saturating_duration_since(Instant::now());
if left.is_zero() {
return Ok(());
}
thread::sleep(left.min(POLL));
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn clones_share_the_flag() {
let token = CancelToken::new();
let clone = token.clone();
assert!(token.check().is_ok());
thread::spawn(move || clone.cancel()).join().unwrap();
assert!(token.is_cancelled());
assert!(matches!(token.check(), Err(Error::Cancelled)));
}
#[test]
fn a_sleep_ends_early_when_cancelled() {
let token = CancelToken::new();
assert!(token.sleep(Duration::from_millis(1)).is_ok());
let canceller = token.clone();
let started = Instant::now();
let handle = thread::spawn(move || {
thread::sleep(Duration::from_millis(100));
canceller.cancel();
});
assert!(matches!(
token.sleep(Duration::from_secs(30)),
Err(Error::Cancelled)
));
assert!(started.elapsed() < Duration::from_secs(5));
handle.join().unwrap();
}
}