almost_enough/stopper.rs
1//! The default cancellation primitive.
2//!
3//! [`Stopper`] is the recommended type for most use cases. It's a simple,
4//! Arc-based cancellation flag with unified clone semantics.
5//!
6//! # Example
7//!
8//! ```rust
9//! use almost_enough::{Stopper, Stop};
10//!
11//! let stop = Stopper::new();
12//! let stop2 = stop.clone(); // Both share the same flag
13//!
14//! assert!(!stop.should_stop());
15//!
16//! stop2.cancel(); // Any clone can cancel
17//! assert!(stop.should_stop());
18//! ```
19//!
20//! # Design
21//!
22//! `Stopper` uses a unified clone model (like tokio's `CancellationToken`):
23//! - Just clone to share - no separate "token" type
24//! - Any clone can call `cancel()`
25//! - Any clone can check `should_stop()`
26//!
27//! This is simpler than source/token split but means you can't prevent
28//! a recipient from cancelling. If you need that, use
29//! [`StopSource`](crate::StopSource)/[`StopRef`](crate::StopRef).
30//!
31//! # Memory Ordering
32//!
33//! Uses Relaxed ordering for best performance. If you need to synchronize
34//! other memory writes with cancellation, use [`SyncStopper`](crate::SyncStopper).
35
36use alloc::sync::Arc;
37use core::sync::atomic::{AtomicBool, Ordering};
38
39use crate::{Stop, StopReason};
40
41/// A cancellation primitive with unified clone semantics.
42///
43/// This is the recommended default for most use cases. Clone it to share
44/// the cancellation state - any clone can cancel or check status.
45///
46/// # Example
47///
48/// ```rust
49/// use almost_enough::{Stopper, Stop};
50///
51/// let stop = Stopper::new();
52///
53/// // Pass a clone to another thread
54/// let stop2 = stop.clone();
55/// std::thread::spawn(move || {
56/// while !stop2.should_stop() {
57/// // do work
58/// break;
59/// }
60/// }).join().unwrap();
61///
62/// // Cancel from original
63/// stop.cancel();
64/// ```
65///
66/// # Performance
67///
68/// - Size: 8 bytes (one pointer)
69/// - `check()`: ~1-2ns (single atomic load with Relaxed ordering)
70/// - `clone()`: atomic increment
71/// - `cancel()`: atomic store
72#[derive(Debug, Clone)]
73pub struct Stopper {
74 cancelled: Arc<AtomicBool>,
75}
76
77impl Stopper {
78 /// Create a new stopper.
79 #[inline]
80 pub fn new() -> Self {
81 Self {
82 cancelled: Arc::new(AtomicBool::new(false)),
83 }
84 }
85
86 /// Create a stopper that is already cancelled.
87 ///
88 /// Useful for testing or when you want to signal immediate stop.
89 #[inline]
90 pub fn cancelled() -> Self {
91 Self {
92 cancelled: Arc::new(AtomicBool::new(true)),
93 }
94 }
95
96 /// Signal all clones to stop.
97 ///
98 /// This is idempotent - calling it multiple times has no additional effect.
99 #[inline]
100 pub fn cancel(&self) {
101 self.cancelled.store(true, Ordering::Relaxed);
102 }
103
104 /// Check if cancellation has been requested.
105 #[inline]
106 pub fn is_cancelled(&self) -> bool {
107 self.cancelled.load(Ordering::Relaxed)
108 }
109}
110
111impl Default for Stopper {
112 fn default() -> Self {
113 Self::new()
114 }
115}
116
117impl Stop for Stopper {
118 #[inline]
119 fn check(&self) -> Result<(), StopReason> {
120 if self.cancelled.load(Ordering::Relaxed) {
121 Err(StopReason::Cancelled)
122 } else {
123 Ok(())
124 }
125 }
126
127 #[inline]
128 fn should_stop(&self) -> bool {
129 self.cancelled.load(Ordering::Relaxed)
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136
137 #[test]
138 fn stopper_basic() {
139 let stop = Stopper::new();
140 assert!(!stop.is_cancelled());
141 assert!(!stop.should_stop());
142 assert!(stop.check().is_ok());
143
144 stop.cancel();
145
146 assert!(stop.is_cancelled());
147 assert!(stop.should_stop());
148 assert_eq!(stop.check(), Err(StopReason::Cancelled));
149 }
150
151 #[test]
152 fn stopper_cancelled_constructor() {
153 let stop = Stopper::cancelled();
154 assert!(stop.is_cancelled());
155 assert!(stop.should_stop());
156 }
157
158 #[test]
159 fn stopper_clone_shares_state() {
160 let stop1 = Stopper::new();
161 let stop2 = stop1.clone();
162
163 assert!(!stop1.should_stop());
164 assert!(!stop2.should_stop());
165
166 // Either clone can cancel
167 stop2.cancel();
168
169 assert!(stop1.should_stop());
170 assert!(stop2.should_stop());
171 }
172
173 #[test]
174 fn stopper_is_default() {
175 let stop: Stopper = Default::default();
176 assert!(!stop.is_cancelled());
177 }
178
179 #[test]
180 fn stopper_is_send_sync() {
181 fn assert_send_sync<T: Send + Sync>() {}
182 assert_send_sync::<Stopper>();
183 }
184
185 #[test]
186 fn stopper_can_outlive_original() {
187 let stop2 = {
188 let stop1 = Stopper::new();
189 stop1.clone()
190 };
191 // Original is dropped, but clone still works
192 assert!(!stop2.should_stop());
193 }
194
195 #[test]
196 fn cancel_is_idempotent() {
197 let stop = Stopper::new();
198 stop.cancel();
199 stop.cancel();
200 stop.cancel();
201 assert!(stop.is_cancelled());
202 }
203}