almost_enough/tree.rs
1//! Hierarchical cancellation with tree structure.
2//!
3//! [`ChildStopper`] provides cancellation that can form parent-child relationships.
4//! When a parent is cancelled, all children are also cancelled. Children can be
5//! cancelled independently without affecting siblings or parents.
6//!
7//! # Overview
8//!
9//! - [`ChildStopper::new()`] - Create a root (no parent)
10//! - [`ChildStopper::with_parent()`] - Create a child of any `Stop` implementation
11//! - [`tree.child()`](ChildStopper::child) - Create a child of this tree node
12//!
13//! # Example
14//!
15//! ```rust
16//! use almost_enough::{ChildStopper, Stop};
17//!
18//! let parent = ChildStopper::new();
19//! let child_a = parent.child();
20//! let child_b = parent.child();
21//!
22//! // Children can be cancelled independently
23//! child_a.cancel();
24//! assert!(child_a.should_stop());
25//! assert!(!child_b.should_stop());
26//!
27//! // Parent cancellation propagates to all children
28//! parent.cancel();
29//! assert!(child_b.should_stop());
30//! ```
31//!
32//! # Grandchildren
33//!
34//! Children can have their own children, creating a cancellation tree:
35//!
36//! ```rust
37//! use almost_enough::{ChildStopper, Stop};
38//!
39//! let grandparent = ChildStopper::new();
40//! let parent = grandparent.child();
41//! let child = parent.child();
42//!
43//! // Grandparent cancellation propagates through the tree
44//! grandparent.cancel();
45//! assert!(parent.should_stop());
46//! assert!(child.should_stop());
47//! ```
48//!
49//! # With Other Stop Types
50//!
51//! You can create a `ChildStopper` as a child of any `Stop` implementation:
52//!
53//! ```rust
54//! use almost_enough::{Stopper, ChildStopper, Stop};
55//!
56//! let root = Stopper::new();
57//! let child = ChildStopper::with_parent(root.clone());
58//!
59//! root.cancel();
60//! assert!(child.should_stop());
61//! ```
62
63use alloc::sync::Arc;
64use core::sync::atomic::{AtomicBool, Ordering};
65
66use crate::{BoxedStop, Stop, StopReason};
67
68/// Inner state for a tree node.
69struct TreeInner {
70 /// This node's own cancellation flag.
71 self_cancelled: AtomicBool,
72 /// Parent to check for inherited cancellation (None for root).
73 parent: Option<BoxedStop>,
74}
75
76impl std::fmt::Debug for TreeInner {
77 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
78 f.debug_struct("TreeInner")
79 .field("self_cancelled", &self.self_cancelled)
80 .field("parent", &self.parent.as_ref().map(|_| "<BoxedStop>"))
81 .finish()
82 }
83}
84
85/// A cancellation primitive with tree-structured parent-child relationships.
86///
87/// `ChildStopper` uses a unified clone model: clone to share, any clone can cancel.
88/// When cancelled, it does NOT affect its parent or siblings - only this node
89/// and any of its children.
90///
91/// # Example
92///
93/// ```rust
94/// use almost_enough::{ChildStopper, Stop};
95///
96/// let parent = ChildStopper::new();
97/// let child = parent.child();
98///
99/// // Clone to share across threads
100/// let child_clone = child.clone();
101///
102/// // Any clone can cancel
103/// child_clone.cancel();
104/// assert!(child.should_stop());
105///
106/// // Parent is not affected
107/// assert!(!parent.should_stop());
108/// ```
109///
110/// # Performance
111///
112/// - Size: 8 bytes (one pointer)
113/// - `check()`: ~5-20ns depending on tree depth (walks parent chain)
114/// - Root nodes: no parent check, similar to `Stopper`
115#[derive(Debug, Clone)]
116pub struct ChildStopper {
117 inner: Arc<TreeInner>,
118}
119
120impl ChildStopper {
121 /// Create a new root tree node (no parent).
122 ///
123 /// This creates a tree root that can have children added via [`child()`](Self::child).
124 ///
125 /// # Example
126 ///
127 /// ```rust
128 /// use almost_enough::{ChildStopper, Stop};
129 ///
130 /// let root = ChildStopper::new();
131 /// let child = root.child();
132 ///
133 /// root.cancel();
134 /// assert!(child.should_stop());
135 /// ```
136 #[inline]
137 pub fn new() -> Self {
138 Self {
139 inner: Arc::new(TreeInner {
140 self_cancelled: AtomicBool::new(false),
141 parent: None,
142 }),
143 }
144 }
145
146 /// Create a new tree node with a parent.
147 ///
148 /// The child will stop if either:
149 /// - [`cancel()`](Self::cancel) is called on this node (or any clone)
150 /// - Any ancestor in the parent chain is cancelled
151 ///
152 /// # Example
153 ///
154 /// ```rust
155 /// use almost_enough::{Stopper, ChildStopper, Stop};
156 ///
157 /// let root = Stopper::new();
158 /// let child = ChildStopper::with_parent(root.clone());
159 ///
160 /// root.cancel();
161 /// assert!(child.should_stop());
162 /// ```
163 #[inline]
164 pub fn with_parent<T: Stop + 'static>(parent: T) -> Self {
165 Self {
166 inner: Arc::new(TreeInner {
167 self_cancelled: AtomicBool::new(false),
168 parent: Some(BoxedStop::new(parent)),
169 }),
170 }
171 }
172
173 /// Create a child of this tree node.
174 ///
175 /// The child will stop if either this node or any ancestor is cancelled.
176 /// Cancelling the child does NOT affect this node.
177 ///
178 /// # Example
179 ///
180 /// ```rust
181 /// use almost_enough::{ChildStopper, Stop};
182 ///
183 /// let parent = ChildStopper::new();
184 /// let child = parent.child();
185 /// let grandchild = child.child();
186 ///
187 /// child.cancel();
188 /// assert!(!parent.should_stop()); // Parent unaffected
189 /// assert!(child.should_stop());
190 /// assert!(grandchild.should_stop()); // Inherits from parent
191 /// ```
192 #[inline]
193 pub fn child(&self) -> ChildStopper {
194 ChildStopper::with_parent(self.clone())
195 }
196
197 /// Cancel this node (and all its children).
198 ///
199 /// This does NOT affect the parent or siblings.
200 #[inline]
201 pub fn cancel(&self) {
202 self.inner.self_cancelled.store(true, Ordering::Relaxed);
203 }
204
205 /// Check if this node is cancelled (either directly or via ancestor).
206 #[inline]
207 pub fn is_cancelled(&self) -> bool {
208 if self.inner.self_cancelled.load(Ordering::Relaxed) {
209 return true;
210 }
211 if let Some(ref parent) = self.inner.parent {
212 parent.should_stop()
213 } else {
214 false
215 }
216 }
217}
218
219impl Default for ChildStopper {
220 fn default() -> Self {
221 Self::new()
222 }
223}
224
225impl Stop for ChildStopper {
226 #[inline]
227 fn check(&self) -> Result<(), StopReason> {
228 if self.inner.self_cancelled.load(Ordering::Relaxed) {
229 return Err(StopReason::Cancelled);
230 }
231 if let Some(ref parent) = self.inner.parent {
232 parent.check()
233 } else {
234 Ok(())
235 }
236 }
237
238 #[inline]
239 fn should_stop(&self) -> bool {
240 self.is_cancelled()
241 }
242}
243
244#[cfg(test)]
245mod tests {
246 use super::*;
247 use crate::Stopper;
248
249 #[test]
250 fn tree_root_basic() {
251 let root = ChildStopper::new();
252 assert!(!root.is_cancelled());
253 assert!(!root.should_stop());
254 assert!(root.check().is_ok());
255
256 root.cancel();
257
258 assert!(root.is_cancelled());
259 assert!(root.should_stop());
260 assert_eq!(root.check(), Err(StopReason::Cancelled));
261 }
262
263 #[test]
264 fn tree_child_inherits_parent() {
265 let parent = ChildStopper::new();
266 let child = parent.child();
267
268 assert!(!child.is_cancelled());
269
270 parent.cancel();
271
272 assert!(child.is_cancelled());
273 }
274
275 #[test]
276 fn tree_child_cancel_independent() {
277 let parent = ChildStopper::new();
278 let child = parent.child();
279
280 child.cancel();
281
282 assert!(child.is_cancelled());
283 assert!(!parent.is_cancelled());
284 }
285
286 #[test]
287 fn tree_siblings_independent() {
288 let parent = ChildStopper::new();
289 let child_a = parent.child();
290 let child_b = parent.child();
291
292 child_a.cancel();
293
294 assert!(child_a.is_cancelled());
295 assert!(!child_b.is_cancelled());
296
297 parent.cancel();
298 assert!(child_b.is_cancelled());
299 }
300
301 #[test]
302 fn tree_grandchild() {
303 let grandparent = ChildStopper::new();
304 let parent = grandparent.child();
305 let child = parent.child();
306
307 assert!(!child.is_cancelled());
308
309 grandparent.cancel();
310 assert!(child.is_cancelled());
311 }
312
313 #[test]
314 fn tree_three_generations() {
315 let g1 = ChildStopper::new();
316 let g2 = g1.child();
317 let g3 = g2.child();
318
319 assert!(!g3.is_cancelled());
320
321 // Cancel middle generation
322 g2.cancel();
323
324 assert!(!g1.is_cancelled());
325 assert!(g2.is_cancelled());
326 assert!(g3.is_cancelled());
327 }
328
329 #[test]
330 fn tree_with_stopper_parent() {
331 let root = Stopper::new();
332 let child = ChildStopper::with_parent(root.clone());
333
334 assert!(!child.is_cancelled());
335
336 root.cancel();
337
338 assert!(child.is_cancelled());
339 }
340
341 #[test]
342 fn tree_clone_shares_state() {
343 let t1 = ChildStopper::new();
344 let t2 = t1.clone();
345
346 t2.cancel();
347
348 assert!(t1.is_cancelled());
349 assert!(t2.is_cancelled());
350 }
351
352 #[test]
353 fn tree_is_send_sync() {
354 fn assert_send_sync<T: Send + Sync>() {}
355 assert_send_sync::<ChildStopper>();
356 }
357
358 #[test]
359 fn tree_is_default() {
360 let t: ChildStopper = Default::default();
361 assert!(!t.is_cancelled());
362 }
363}