use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
use anyhow::Result;
use crate::error::Canceled;
#[derive(Clone)]
pub struct Cancel {
flag: Arc<AtomicBool>,
}
impl Cancel {
#[must_use]
pub fn install() -> Self {
let flag = Arc::new(AtomicBool::new(false));
let handler_flag = Arc::clone(&flag);
if let Err(e) = ctrlc::set_handler(move || {
handler_flag.store(true, Ordering::SeqCst);
}) {
anstream::eprintln!("warning: failed to install signal handler: {e}");
}
Self { flag }
}
#[must_use]
pub fn is_set(&self) -> bool {
self.flag.load(Ordering::SeqCst)
}
pub fn check(&self) -> Result<()> {
if self.is_set() {
Err(Canceled.into())
} else {
Ok(())
}
}
#[cfg(test)]
#[must_use]
pub fn noop() -> Self {
Self {
flag: Arc::new(AtomicBool::new(false)),
}
}
#[cfg(test)]
pub fn trigger(&self) {
self.flag.store(true, Ordering::SeqCst);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn unset_returns_ok() {
let c = Cancel::noop();
assert!(!c.is_set());
assert!(c.check().is_ok());
}
#[test]
fn triggered_returns_canceled() {
let c = Cancel::noop();
c.trigger();
assert!(c.is_set());
let err = c.check().unwrap_err();
assert!(err.downcast_ref::<Canceled>().is_some());
}
#[test]
fn clones_share_flag() {
let a = Cancel::noop();
let b = a.clone();
a.trigger();
assert!(b.is_set());
}
}