Skip to main content

dataflow_rs/base/
cancel.rs

1//! `CancelToken`: a flag a host trips to cancel a run, shared by every clone
2//! and inherited by every child.
3
4use std::fmt;
5use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, Ordering};
7#[cfg(not(target_arch = "wasm32"))]
8use {std::pin::Pin, std::pin::pin, tokio::sync::Notify};
9
10/// A cancellation flag. Clones share one flag; a [`child`](Self::child) has
11/// its own, and is cancelled when it or any ancestor is.
12///
13/// A function reads the run's token through
14/// [`Ctx::cancel`](crate::function::Ctx::cancel), and hands a child to a run
15/// it starts, so cancelling the parent cancels both.
16///
17/// ```
18/// use dataflow_rs::run::CancelToken;
19///
20/// let parent = CancelToken::new();
21/// let child = parent.child();
22/// child.cancel();
23/// assert!(child.is_cancelled() && !parent.is_cancelled());
24/// let other = parent.child();
25/// parent.cancel();
26/// assert!(other.is_cancelled());
27/// ```
28#[derive(Clone, Default)]
29pub struct CancelToken {
30    inner: Arc<Inner>,
31}
32
33/// One token's flag and the chain of its ancestors'.
34#[derive(Default)]
35struct Inner {
36    flag: AtomicBool,
37    parent: Option<Arc<Self>>,
38    /// The tasks awaiting [`CancelToken::cancelled`] on this token, woken by
39    /// its `cancel`. A child's waiters wait on every ancestor's too.
40    #[cfg(not(target_arch = "wasm32"))]
41    waiters: Notify,
42}
43
44impl CancelToken {
45    /// A token not yet cancelled, with no parent.
46    pub fn new() -> Self {
47        Self::default()
48    }
49
50    /// Cancels this token, its clones and every child made from them.
51    #[cfg_attr(
52        not(target_arch = "wasm32"),
53        doc = "Every task awaiting [`cancelled`](Self::cancelled) on them is woken."
54    )]
55    pub fn cancel(&self) {
56        self.inner.flag.store(true, Ordering::Release);
57        #[cfg(not(target_arch = "wasm32"))]
58        self.inner.waiters.notify_waiters();
59    }
60
61    /// Whether this token or any of its ancestors was cancelled.
62    pub fn is_cancelled(&self) -> bool {
63        let mut at = Some(&self.inner);
64        while let Some(inner) = at {
65            if inner.flag.load(Ordering::Acquire) {
66                return true;
67            }
68            at = inner.parent.as_ref();
69        }
70        false
71    }
72
73    /// A token cancelled with this one, or on its own without cancelling
74    /// this one.
75    #[must_use]
76    pub fn child(&self) -> Self {
77        Self {
78            inner: Arc::new(Inner {
79                parent: Some(Arc::clone(&self.inner)),
80                ..Inner::default()
81            }),
82        }
83    }
84
85    /// Resolves once this token or any of its ancestors is cancelled, at
86    /// once if one already is: for a host's own races, such as a
87    /// `tokio::select!` between its I/O and the run's token. Native targets
88    /// only.
89    #[cfg(not(target_arch = "wasm32"))]
90    pub async fn cancelled(&self) {
91        tripped(&self.inner).await;
92    }
93}
94
95/// Resolves once `inner`'s flag or an ancestor's is set. It registers on
96/// `inner`'s waiters before it reads the flag, so a `cancel` between the two
97/// still wakes it, and awaits the parent's the same way, boxed: a chain of
98/// children has no fixed depth.
99#[cfg(not(target_arch = "wasm32"))]
100async fn tripped(inner: &Inner) {
101    let mut own = pin!(inner.waiters.notified());
102    own.as_mut().enable();
103    if inner.flag.load(Ordering::Acquire) {
104        return;
105    }
106    match &inner.parent {
107        None => own.await,
108        Some(parent) => {
109            futures_util::future::select(own, boxed(parent)).await;
110        }
111    }
112}
113
114/// [`tripped`] for an ancestor, boxed to a named type so the recursion has
115/// one.
116#[cfg(not(target_arch = "wasm32"))]
117fn boxed(inner: &Inner) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
118    Box::pin(tripped(inner))
119}
120
121impl fmt::Debug for CancelToken {
122    /// Whether it is cancelled.
123    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
124        f.debug_struct("CancelToken")
125            .field("cancelled", &self.is_cancelled())
126            .finish_non_exhaustive()
127    }
128}
129
130#[cfg(test)]
131mod tests {
132    use super::*;
133
134    #[test]
135    fn a_new_token_is_not_cancelled_and_cancel_trips_every_clone() {
136        let token = CancelToken::new();
137        let clone = token.clone();
138        assert!(!token.is_cancelled());
139        clone.cancel();
140        assert!(token.is_cancelled() && clone.is_cancelled());
141    }
142
143    #[test]
144    fn a_child_trips_with_any_ancestor_and_alone_without_them() {
145        let root = CancelToken::new();
146        let child = root.child();
147        let grandchild = child.child();
148        let sibling = root.child();
149        grandchild.cancel();
150        assert!(grandchild.is_cancelled());
151        assert!(!child.is_cancelled() && !root.is_cancelled() && !sibling.is_cancelled());
152        root.cancel();
153        assert!(child.is_cancelled() && sibling.is_cancelled());
154        assert!(root.child().is_cancelled(), "a child of a cancelled token");
155    }
156
157    /// Polls `future` once, with a waker that does nothing.
158    #[cfg(not(target_arch = "wasm32"))]
159    fn poll_once<F: Future>(future: Pin<&mut F>) -> bool {
160        let waker = std::task::Waker::noop();
161        future
162            .poll(&mut std::task::Context::from_waker(waker))
163            .is_ready()
164    }
165
166    #[test]
167    #[cfg(not(target_arch = "wasm32"))]
168    fn cancelled_is_pending_until_the_token_or_an_ancestor_is_cancelled() {
169        let root = CancelToken::new();
170        let child = root.child();
171        let mut on_child = pin!(child.cancelled());
172        let mut on_root = pin!(root.cancelled());
173        assert!(!poll_once(on_child.as_mut()) && !poll_once(on_root.as_mut()));
174        root.cancel();
175        assert!(poll_once(on_child.as_mut()) && poll_once(on_root.as_mut()));
176        // Already cancelled: ready at the first poll.
177        assert!(poll_once(pin!(child.child().cancelled())));
178    }
179
180    #[test]
181    fn debug_shows_whether_it_is_cancelled() {
182        let token = CancelToken::new();
183        assert_eq!(format!("{token:?}"), "CancelToken { cancelled: false, .. }");
184    }
185}