Skip to main content

kithara_platform/common/cancel/
token.rs

1use std::{fmt, sync::Arc};
2
3use super::{
4    node::{Node, Slot},
5    wait::Cancelled,
6};
7
8/// A handle into a cancel subtree.
9///
10/// [`Clone`] yields the **same** identity (same node), like the legacy token. A
11/// `cancel()` cancels this token's own subtree (epoch/fetch cancels are
12/// legitimate); ancestors and siblings are unaffected. Two constructors mint a
13/// fresh subtree root instead of deriving from a parent —
14/// [`root`](CancelToken::root), the owning master a subsystem holds and cancels
15/// on teardown, and [`never`](CancelToken::never), a sentinel that is never
16/// cancelled. Both are covered by the `cancel_root_sites` guard.
17#[derive(Clone)]
18pub struct CancelToken {
19    node: Arc<Node>,
20}
21
22impl CancelToken {
23    /// Cancel this token's subtree.
24    pub fn cancel(&self) {
25        self.node.cancel();
26    }
27
28    /// Future that resolves once this token's subtree is cancelled. Cancel-safe
29    /// in `tokio::select!`: dropping it unregisters its slot.
30    #[must_use]
31    pub fn cancelled(&self) -> Cancelled<'_> {
32        Cancelled::new(&self.node)
33    }
34
35    /// Derive a child token rooted at this token's subtree.
36    #[must_use]
37    pub fn child(&self) -> Self {
38        Self {
39            node: Node::child(&self.node),
40        }
41    }
42
43    #[must_use]
44    pub fn is_cancelled(&self) -> bool {
45        self.node.is_cancelled()
46    }
47
48    /// A token that is never cancelled — a placeholder where a token is required
49    /// but no cancellation source exists. The sentinel sibling of
50    /// [`root`](CancelToken::root); both mint a fresh subtree root.
51    #[must_use]
52    pub fn never() -> Self {
53        Self { node: Node::root() }
54    }
55
56    /// Register a synchronous waker fired when this token is cancelled — the sync
57    /// counterpart to [`cancelled`](CancelToken::cancelled) for a thread parked
58    /// on a (flash-aware) `Condvar`/`Notify`. Registered on THIS node only; an
59    /// ancestor `cancel()` reaches it through the down-propagation drain.
60    ///
61    /// The waker runs on the cancelling thread: keep it cheap, non-blocking, and
62    /// idempotent. If the token is already cancelled it fires immediately. The
63    /// returned guard unregisters the waker on drop — hold it for the wait's
64    /// lifetime.
65    pub fn on_cancel<F>(&self, waker: F) -> CancelWakerGuard
66    where
67        F: Fn() + Send + Sync + 'static,
68    {
69        let id = self.node.register(Slot::Sync(Arc::new(waker)));
70        CancelWakerGuard {
71            node: id.map(|id| (Arc::clone(&self.node), id)),
72        }
73    }
74
75    /// Mint a fresh root of a new cancel subtree — the owning master a subsystem
76    /// holds and cancels on teardown. `Drop` is passive: dropping the root does
77    /// **not** cancel its subtree; teardown is an explicit `cancel()`. Minted only
78    /// at owner sites (enforced by the `cancel_root_sites` guard). For a token that
79    /// is structurally required but never cancelled, use
80    /// [`never`](CancelToken::never) instead.
81    #[must_use]
82    pub fn root() -> Self {
83        Self { node: Node::root() }
84    }
85}
86
87impl fmt::Debug for CancelToken {
88    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
89        f.debug_struct("CancelToken")
90            .field("cancelled", &self.is_cancelled())
91            .finish()
92    }
93}
94
95/// Drop guard returned by [`CancelToken::on_cancel`]. Unregisters the waker on
96/// drop; holding it keeps the cancel-wake live.
97#[must_use = "dropping the guard immediately unregisters the cancel waker"]
98pub struct CancelWakerGuard {
99    node: Option<(Arc<Node>, u64)>,
100}
101
102impl Drop for CancelWakerGuard {
103    fn drop(&mut self) {
104        if let Some((node, id)) = &self.node {
105            node.unregister(*id);
106        }
107    }
108}
109
110impl fmt::Debug for CancelWakerGuard {
111    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        f.debug_struct("CancelWakerGuard")
113            .field("registered", &self.node.is_some())
114            .finish()
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use std::{
121        sync::{
122            Arc,
123            atomic::{AtomicUsize, Ordering},
124        },
125        time::Duration,
126    };
127
128    use kithara_test_utils::kithara;
129    use tokio::{spawn, task, time as tokio_time};
130
131    use super::CancelToken;
132
133    #[kithara::test(timeout(Duration::from_secs(5)))]
134    fn fresh_token_not_cancelled() {
135        let c = CancelToken::root();
136        assert!(!c.is_cancelled());
137    }
138
139    #[kithara::test(timeout(Duration::from_secs(5)))]
140    fn cancel_sets_lock_free_flag() {
141        let c = CancelToken::root();
142        c.cancel();
143        assert!(c.is_cancelled());
144    }
145
146    #[kithara::test(timeout(Duration::from_secs(5)))]
147    fn child_cancel_does_not_cancel_parent() {
148        // WHY: cancelling a child sets only the child's subtree, never the
149        let parent = CancelToken::root();
150        let child = parent.child();
151        child.cancel();
152        assert!(child.is_cancelled());
153        assert!(
154            !parent.is_cancelled(),
155            "child cancel must not cancel the parent"
156        );
157    }
158
159    #[kithara::test(timeout(Duration::from_secs(5)))]
160    fn child_cancel_does_not_cancel_sibling() {
161        let parent = CancelToken::root();
162        let a = parent.child();
163        let b = parent.child();
164        a.cancel();
165        assert!(a.is_cancelled());
166        assert!(!b.is_cancelled(), "sibling cancel must stay independent");
167        assert!(!parent.is_cancelled());
168    }
169
170    #[kithara::test(timeout(Duration::from_secs(5)))]
171    fn clone_shares_cancel_identity() {
172        // WHY: Clone is the SAME token (same node), so cancelling a clone is
173        // observed by the original — distinct from child().
174        let token = CancelToken::never();
175        let twin = token.clone();
176        twin.cancel();
177        assert!(
178            token.is_cancelled(),
179            "clone shares identity with the original"
180        );
181    }
182
183    #[kithara::test(timeout(Duration::from_secs(5)))]
184    fn child_observes_own_cancel() {
185        let parent = CancelToken::root();
186        let child = parent.child();
187        child.cancel();
188        assert!(child.is_cancelled());
189    }
190
191    /// A worker token derived via `child()` must observe a master `cancel()`
192    /// through its lock-free read, even though nobody calls the worker token's
193    /// own `cancel()`.
194    #[kithara::test(timeout(Duration::from_secs(5)))]
195    fn child_observes_parent_cancel_lock_free() {
196        let master = CancelToken::root();
197        let worker = master.child();
198        assert!(!worker.is_cancelled());
199
200        std::thread::scope(|s| {
201            s.spawn(|| master.cancel());
202        });
203
204        assert!(
205            worker.is_cancelled(),
206            "worker child must observe master cancel via propagate-down"
207        );
208    }
209
210    #[kithara::test(timeout(Duration::from_secs(5)))]
211    fn grandchild_observes_master_cancel_lock_free() {
212        let master = CancelToken::root();
213        let mid = master.child();
214        let leaf = mid.child();
215
216        master.cancel();
217        assert!(mid.is_cancelled());
218        assert!(leaf.is_cancelled());
219    }
220
221    /// PARENT-LIVENESS PIN: drop every clone of an intermediate token, then
222    /// cancel the root — the grandchild (held alive) must still observe it. The
223    /// legacy grandchild test keeps `mid` alive and would not catch a missing
224    /// `parent` liveness field; this one drops it.
225    #[kithara::test(timeout(Duration::from_secs(5)))]
226    fn root_cancel_reaches_grandchild_after_intermediate_dropped() {
227        let master = CancelToken::root();
228        let leaf = {
229            let mid = master.child();
230            mid.child()
231            // `mid` dropped here — its node must stay alive via `leaf.parent`.
232        };
233        assert!(!leaf.is_cancelled());
234        master.cancel();
235        assert!(
236            leaf.is_cancelled(),
237            "root cancel must reach the grandchild after the intermediate token dropped"
238        );
239    }
240
241    #[kithara::test(timeout(Duration::from_secs(5)))]
242    fn repeat_cancel_is_idempotent() {
243        let c = CancelToken::root();
244        c.cancel();
245        c.cancel();
246        assert!(c.is_cancelled());
247    }
248
249    #[kithara::test(timeout(Duration::from_secs(5)))]
250    fn late_child_born_cancelled() {
251        let master = CancelToken::root();
252        master.cancel();
253        let child = master.child();
254        assert!(
255            child.is_cancelled(),
256            "a child born after the parent cancelled must be born cancelled"
257        );
258    }
259
260    #[kithara::test(timeout(Duration::from_secs(5)))]
261    fn child_churn_then_cancel_reaches_survivors() {
262        // End-to-end: after heavy per-fetch/epoch child churn, a master cancel
263        // still reaches the live children. The dead-Weak sweep BOUND itself is
264        // pinned at the node layer (`node::tests::child_churn_sweeps_dead_weaks`),
265        // which is the only place the private `children` len is observable.
266        let master = CancelToken::root();
267        for _ in 0..1000 {
268            let _ = master.child();
269        }
270        let survivor = master.child();
271        let _second = master.child();
272        master.cancel();
273        assert!(survivor.is_cancelled());
274    }
275
276    #[kithara::test(tokio, timeout(Duration::from_secs(5)))]
277    async fn async_cancelled_resolves_on_self_cancel() {
278        // Node mechanism: cancelled() resolves when this node cancels; the flag
279        let c = CancelToken::never();
280        let c2 = c.clone();
281        let handle = spawn(async move {
282            c2.cancelled().await;
283            c2.is_cancelled()
284        });
285        task::yield_now().await;
286
287        c.cancel();
288
289        let flag = tokio_time::timeout(Duration::from_secs(2), handle)
290            .await
291            .expect("cancelled() must resolve within the test timeout")
292            .expect("spawned cancellation task must not panic");
293        assert!(flag, "flag must be visible once cancelled() resolves");
294    }
295
296    fn counter() -> (Arc<AtomicUsize>, impl Fn() + Send + Sync + 'static) {
297        let n = Arc::new(AtomicUsize::new(0));
298        let n2 = Arc::clone(&n);
299        (n, move || {
300            n2.fetch_add(1, Ordering::SeqCst);
301        })
302    }
303
304    #[kithara::test(timeout(Duration::from_secs(5)))]
305    fn on_cancel_fires_on_self_cancel() {
306        let c = CancelToken::root();
307        let child = c.child();
308        let (fired, waker) = counter();
309        let _guard = child.on_cancel(waker);
310        assert_eq!(fired.load(Ordering::SeqCst), 0);
311        child.cancel();
312        assert_eq!(fired.load(Ordering::SeqCst), 1, "waker must fire on cancel");
313    }
314
315    #[kithara::test(timeout(Duration::from_secs(5)))]
316    fn on_cancel_fires_on_ancestor_cancel() {
317        // WHY: a consumer holding a CHILD token must be woken when a master
318        // cancels — the down-propagation drain reaches the child's node.
319        let master = CancelToken::root();
320        let child = master.child();
321        let (fired, waker) = counter();
322        let _guard = child.on_cancel(waker);
323        master.cancel();
324        assert!(
325            fired.load(Ordering::SeqCst) >= 1,
326            "ancestor cancel must wake a descendant's sync waker"
327        );
328    }
329
330    #[kithara::test(timeout(Duration::from_secs(5)))]
331    fn on_cancel_already_cancelled_fires_immediately() {
332        let c = CancelToken::never();
333        c.cancel();
334        let (fired, waker) = counter();
335        let _guard = c.on_cancel(waker);
336        assert_eq!(
337            fired.load(Ordering::SeqCst),
338            1,
339            "registering on an already-cancelled token fires at once"
340        );
341    }
342
343    #[kithara::test(timeout(Duration::from_secs(5)))]
344    fn on_cancel_guard_drop_unregisters() {
345        let c = CancelToken::never();
346        let (fired, waker) = counter();
347        let guard = c.on_cancel(waker);
348        drop(guard);
349        c.cancel();
350        assert_eq!(
351            fired.load(Ordering::SeqCst),
352            0,
353            "a dropped guard must not fire on a later cancel"
354        );
355    }
356
357    #[kithara::test(timeout(Duration::from_secs(5)))]
358    fn on_cancel_sibling_cancel_does_not_fire() {
359        let parent = CancelToken::root();
360        let a = parent.child();
361        let b = parent.child();
362        let (fired, waker) = counter();
363        let _guard = a.on_cancel(waker);
364        b.cancel();
365        assert_eq!(
366            fired.load(Ordering::SeqCst),
367            0,
368            "a sibling cancel must not fire this token's waker"
369        );
370        assert!(!a.is_cancelled());
371    }
372}