Skip to main content

base_scheduler/
cancellation.rs

1// Released under the MIT License.
2// Copyright, 2026, by Samuel Williams.
3
4use crate::Cancelled;
5use event_listener::Event;
6use std::future::pending;
7use std::sync::atomic::{AtomicBool, Ordering};
8use std::sync::{Arc, Mutex, PoisonError, Weak};
9
10struct Node {
11    cancelled: AtomicBool,
12    event: Event,
13    // Serialize child registration and cancellation across this signal family.
14    tree: Arc<Mutex<()>>,
15    // Children retain ancestors, but ancestors only hold weak child references.
16    parent: Option<Arc<Node>>,
17    // This node's slot in its parent's children.
18    index: usize,
19    children: Mutex<Children>,
20}
21
22// Weak child registrations, addressed by a slot each child records.
23#[derive(Default)]
24struct Children {
25    slots: Vec<Option<Weak<Node>>>,
26    free: Vec<usize>,
27}
28
29impl Children {
30    #[cfg(test)]
31    fn is_empty(&self) -> bool {
32        self.slots.iter().all(Option::is_none)
33    }
34    fn next(&self) -> usize {
35        self.free.last().copied().unwrap_or(self.slots.len())
36    }
37    fn insert(&mut self, child: Weak<Node>) {
38        match self.free.pop() {
39            Some(index) => self.slots[index] = Some(child),
40            None => self.slots.push(Some(child)),
41        }
42    }
43    fn remove(&mut self, index: usize) {
44        // A live child keeps its slot until it drops, and a cancelled parent
45        // takes every slot and registers no further children.
46        if let Some(slot) = self.slots.get_mut(index) {
47            *slot = None;
48            self.free.push(index);
49        }
50    }
51}
52
53impl Node {
54    fn new(tree: Arc<Mutex<()>>, parent: Option<Arc<Node>>, index: usize) -> Self {
55        let event = Event::new();
56        // Allocate shared event state during setup, rather than on the first wait.
57        event.notify(0);
58        Self {
59            cancelled: AtomicBool::new(false),
60            event,
61            tree,
62            parent,
63            index,
64            children: Mutex::new(Children::default()),
65        }
66    }
67}
68
69impl Drop for Node {
70    fn drop(&mut self) {
71        let mut index = self.index;
72        let mut parent = self.parent.take();
73        // Release an exclusively owned ancestor chain without recursive drops.
74        while let Some(node) = parent {
75            // A slot is reused only after its child's destruction removes it, so the index cannot alias a later registration.
76            node.children
77                .lock()
78                .unwrap_or_else(PoisonError::into_inner)
79                .remove(index);
80            index = node.index;
81            match Arc::try_unwrap(node) {
82                Ok(mut node) => {
83                    parent = node.parent.take();
84                }
85                Err(_) => break,
86            }
87        }
88    }
89}
90
91/// A runtime-independent, cooperative cancellation signal.
92///
93/// Clones share a persistent cancellation state. Child signals receive ancestor cancellation, but cancelling a child does not cancel its parent or siblings. Dropping a signal does not request cancellation. Signals neither own tasks nor abort futures; callers decide how to respond and separately await completion.
94///
95/// ```
96/// use base_scheduler::{Cancellation, Cancelled};
97///
98/// let system = Cancellation::new();
99/// let server = system.child();
100/// assert!(server.cancel());
101/// assert_eq!(server.check(), Err(Cancelled));
102/// assert!(!system.is_cancelled());
103/// assert!(system.cancel());
104/// ```
105#[derive(Clone)]
106pub struct Cancellation {
107    node: Option<Arc<Node>>,
108}
109
110impl Cancellation {
111    /// Create an independent, uncancelled signal.
112    pub fn new() -> Self {
113        Self {
114            node: Some(Arc::new(Node::new(Arc::new(Mutex::new(())), None, 0))),
115        }
116    }
117
118    /// Create a signal which cannot be cancelled and requires no allocation. `cancel` always returns false and `cancelled` remains pending forever.
119    pub const fn never() -> Self {
120        Self { node: None }
121    }
122
123    /// Whether this signal can never be cancelled. This is true for [`Self::never`] and its clones; its children are independently cancellable. Backends can omit cancellation registration for these signals.
124    pub const fn is_never(&self) -> bool {
125        self.node.is_none()
126    }
127
128    /// Create a signal cancelled by this signal or any of its ancestors. A child of an already-cancelled signal starts cancelled. A child of a never-cancelled signal is an independent, cancellable signal.
129    pub fn child(&self) -> Self {
130        let Some(parent) = &self.node else {
131            return Self::new();
132        };
133        let _tree = parent.tree.lock().unwrap_or_else(PoisonError::into_inner);
134        let mut children = parent
135            .children
136            .lock()
137            .unwrap_or_else(PoisonError::into_inner);
138        let child = Arc::new(Node::new(
139            Arc::clone(&parent.tree),
140            Some(Arc::clone(parent)),
141            children.next(),
142        ));
143        if parent.cancelled.load(Ordering::Acquire) {
144            child.cancelled.store(true, Ordering::Release);
145        } else {
146            children.insert(Arc::downgrade(&child));
147        }
148        Self { node: Some(child) }
149    }
150
151    /// Request cancellation of this signal and all its descendants. Returns true only for the first request on this signal. Repeated requests never escalate. Waiters are woken after the entire family lock is released. Concurrent observers may see propagation in progress, but all descendants are cancelled when this call returns. This does not wait for their work.
152    pub fn cancel(&self) -> bool {
153        let Some(root) = &self.node else {
154            return false;
155        };
156        let tree = root.tree.lock().unwrap_or_else(PoisonError::into_inner);
157        if root.cancelled.load(Ordering::Acquire) {
158            return false;
159        }
160        let mut pending = vec![Arc::clone(root)];
161        let mut cancelled = Vec::new();
162        while let Some(node) = pending.pop() {
163            node.cancelled.store(true, Ordering::Release);
164            pending.extend(
165                std::mem::take(&mut *node.children.lock().unwrap_or_else(PoisonError::into_inner))
166                    .slots
167                    .into_iter()
168                    .flatten()
169                    .filter_map(|child| child.upgrade()),
170            );
171            cancelled.push(node);
172        }
173        drop(tree);
174        for node in cancelled {
175            node.event.notify(usize::MAX);
176        }
177        true
178    }
179
180    /// Whether cancellation has been requested.
181    #[inline]
182    pub fn is_cancelled(&self) -> bool {
183        self.node
184            .as_ref()
185            .is_some_and(|node| node.cancelled.load(Ordering::Acquire))
186    }
187
188    /// Check for cancellation at an explicit cooperative cancellation point.
189    #[inline]
190    pub fn check(&self) -> Result<(), Cancelled> {
191        if self.is_cancelled() {
192            Err(Cancelled)
193        } else {
194            Ok(())
195        }
196    }
197
198    /// Wait for cancellation using standard future polling and wakeups. This future is immediately ready if already cancelled. Dropping the wait unregisters its listener without cancelling the signal or other waiters. Listener storage is retained inline in the future, without a separate allocation.
199    pub async fn cancelled(&self) {
200        let Some(node) = &self.node else {
201            return pending().await;
202        };
203        event_listener::listener!(node.event => listener);
204        // Register before checking to preserve a racing cancellation notification.
205        if !node.cancelled.load(Ordering::Acquire) {
206            listener.await;
207        }
208    }
209}
210
211impl Default for Cancellation {
212    fn default() -> Self {
213        Self::new()
214    }
215}
216
217#[cfg(test)]
218mod tests;