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}