use std::sync::Arc;
use std::sync::atomic::{AtomicBool, Ordering};
#[derive(Clone, Debug)]
pub struct Halt(Arc<AtomicBool>);
impl Halt {
pub fn new() -> Self {
Self(Arc::new(AtomicBool::new(false)))
}
pub fn from_arc(flag: Arc<AtomicBool>) -> Self {
Self(flag)
}
pub fn as_arc(&self) -> &Arc<AtomicBool> {
&self.0
}
pub fn cancel(&self) {
self.0.store(true, Ordering::Relaxed);
}
pub fn is_cancelled(&self) -> bool {
self.0.load(Ordering::Relaxed)
}
}
impl Default for Halt {
fn default() -> Self {
Self::new()
}
}
pub const POLL_INTERVAL: std::time::Duration = std::time::Duration::from_millis(250);
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fresh_is_not_cancelled() {
let h = Halt::new();
assert!(!h.is_cancelled());
}
#[test]
fn cancel_flips_state() {
let h = Halt::new();
assert!(!h.is_cancelled());
h.cancel();
assert!(h.is_cancelled());
}
#[test]
fn cancel_is_idempotent() {
let h = Halt::new();
h.cancel();
h.cancel();
assert!(h.is_cancelled());
}
#[test]
fn clone_shares_state() {
let original = Halt::new();
let cloned = original.clone();
assert!(!original.is_cancelled());
assert!(!cloned.is_cancelled());
cloned.cancel();
assert!(original.is_cancelled());
assert!(cloned.is_cancelled());
}
#[test]
fn clone_shares_state_reverse_direction() {
let original = Halt::new();
let cloned = original.clone();
original.cancel();
assert!(cloned.is_cancelled());
}
#[test]
fn clone_shares_state_across_threads() {
let h = Halt::new();
let h2 = h.clone();
let handle = std::thread::spawn(move || {
h2.cancel();
});
handle.join().unwrap();
assert!(h.is_cancelled());
}
#[test]
fn from_arc_shares_state() {
let arc = Arc::new(AtomicBool::new(false));
let halt = Halt::from_arc(arc.clone());
assert!(!halt.is_cancelled());
assert!(!arc.load(Ordering::Relaxed));
halt.cancel();
assert!(arc.load(Ordering::Relaxed));
let arc2 = Arc::new(AtomicBool::new(false));
let halt2 = Halt::from_arc(arc2.clone());
arc2.store(true, Ordering::Relaxed);
assert!(halt2.is_cancelled());
}
#[test]
fn as_arc_returns_backing_flag() {
let halt = Halt::new();
let arc = halt.as_arc().clone();
assert!(!halt.is_cancelled());
arc.store(true, Ordering::Relaxed);
assert!(halt.is_cancelled());
}
#[test]
fn default_produces_uncancelled_token() {
let h = Halt::default();
assert!(
!h.is_cancelled(),
"Default::default() must produce uncancelled token"
);
}
#[test]
fn multiple_clones_all_share_same_flag() {
let h0 = Halt::new();
let h1 = h0.clone();
let h2 = h0.clone();
let h3 = h1.clone();
assert!(!h0.is_cancelled());
assert!(!h1.is_cancelled());
assert!(!h2.is_cancelled());
assert!(!h3.is_cancelled());
h2.cancel();
assert!(h0.is_cancelled());
assert!(h1.is_cancelled());
assert!(h3.is_cancelled());
}
#[test]
fn is_cancelled_is_non_destructive() {
let h = Halt::new();
h.cancel();
assert!(h.is_cancelled());
assert!(h.is_cancelled(), "second read must also return true");
assert!(h.is_cancelled(), "third read must also return true");
}
#[test]
fn from_arc_and_as_arc_are_inverses() {
let original = Arc::new(AtomicBool::new(false));
let halt = Halt::from_arc(original.clone());
halt.cancel();
assert!(
original.load(Ordering::Relaxed),
"cancel() must be visible via the original Arc"
);
let retrieved = halt.as_arc();
assert!(
std::ptr::eq(Arc::as_ptr(retrieved), Arc::as_ptr(&original)),
"as_arc must return the same Arc pointer as was passed to from_arc"
);
}
#[test]
fn poll_interval_is_250ms() {
assert_eq!(
POLL_INTERVAL,
std::time::Duration::from_millis(250),
"POLL_INTERVAL must be 250ms for the guaranteed ~quarter-second cancel latency"
);
}
#[test]
fn clone_of_cancelled_halt_is_also_cancelled() {
let h = Halt::new();
h.cancel();
let cloned = h.clone();
assert!(
cloned.is_cancelled(),
"clone of a cancelled Halt must itself be cancelled"
);
}
}