Skip to main content

wait_token/
lib.rs

1use futures::{FutureExt, StreamExt};
2use std::{
3    future::Future,
4    ops::Deref,
5    pin::Pin,
6    sync::{
7        Arc,
8        atomic::{self, AtomicBool},
9    },
10    time::Duration,
11};
12
13#[derive(Debug, Clone)]
14pub struct WaitTokenRepeat<Tk> {
15    tk: Tk,
16    frq: Duration,
17}
18impl WaitTokenRepeat<WaitToken> {
19    pub async fn next(&mut self) -> bool {
20        let result = tokio::select! {
21            _ = tokio::time::sleep(self.frq) =>  {true}
22            _ = self.tk.cancelled() =>  {false}
23        };
24        result
25    }
26}
27
28#[derive(Debug, Default)]
29pub struct WaitTokenGuard {
30    tk: WaitToken,
31}
32impl Drop for WaitTokenGuard {
33    fn drop(&mut self) {
34        self.tk.cancel();
35    }
36}
37impl Deref for WaitTokenGuard {
38    type Target = WaitToken;
39
40    fn deref(&self) -> &Self::Target {
41        &self.tk
42    }
43}
44impl WaitTokenGuard {
45    pub fn new() -> Self {
46        Self {
47            tk: WaitToken::new(),
48        }
49    }
50    pub fn clone_tk(&self) -> WaitToken {
51        self.deref().clone()
52    }
53}
54
55/// WaitToken (aka CancellationToken) is an object used for 'cancellation' events.
56///
57/// Example:
58/// ```rust
59/// // Create a new token
60/// let cancellation = WaitToken::new();
61///
62/// // Clone token to use it in another thread. You can call cancel on this object too!
63/// let cancellation_clone = cancellation.clone();
64/// tokio::spawn(async move {
65///     // wait for token to cancel
66///     cancellation_clone.await;
67///     println!("Finished");
68/// })
69///
70/// // Child tokens will cancel when parent is cancelled.
71/// // 'cancel()' on child tokens will not cancel parent
72/// let child = cancellation.make_child_token();
73/// tokio::spawn(async move {
74///     // Wait for token to cancel
75///     child.await;
76///     println!("Child is finished");
77/// })
78///
79/// println!("Cancelled");
80/// cancellation.cancel();
81///
82/// // This await is just for demonstration. No need to await after cancel
83/// // Note that 'await' takes by-value so you may need to clone
84/// cancellation.clone().await;
85///
86/// // Undo cancellation of token
87/// cancellation.reset();
88///
89/// // This await will not return because token is not cancelled anymore
90/// cancellation.clone().await;
91///
92/// // This is unreachable
93/// println!("Something went wrong!");
94/// ```
95#[derive(Debug, Clone)]
96pub struct WaitToken {
97    inner: Arc<WaitTokenRaw>,
98}
99
100pub type CancellationToken = WaitToken;
101
102#[derive(Debug)]
103struct WaitTokenRaw {
104    state: AtomicBool,
105    parent: Option<WaitToken>,
106    future: Option<tokio::sync::Notify>,
107}
108
109impl WaitTokenRaw {
110    pub fn is_cancelled(&self) -> bool {
111        if let Some(parent) = &self.parent {
112            if parent.is_cancelled() {
113                return true;
114            }
115        }
116        self.state.load(atomic::Ordering::Acquire)
117    }
118
119    fn notified<'a: 'b, 'b>(&'a self) -> tokio::sync::futures::Notified<'b> {
120        if let Some(parent) = &self.parent {
121            return parent.inner.notified();
122        }
123        self.future.as_ref().unwrap().notified()
124    }
125}
126
127#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum CancelState {
129    AlreadyCancelled,
130    Cancelled,
131}
132impl CancelState {
133    pub fn already_cancelled(self) -> bool {
134        self == CancelState::AlreadyCancelled
135    }
136    pub fn just_cancelled(self) -> bool {
137        self == CancelState::Cancelled
138    }
139}
140
141impl WaitToken {
142    /// Create a new token
143    pub fn new() -> Self {
144        Self {
145            inner: Arc::new(WaitTokenRaw {
146                state: false.into(),
147                parent: None,
148                future: Some(tokio::sync::Notify::new()),
149            }),
150        }
151    }
152
153    /// Spawns background fetch to listem SIGTERM & SIGINT events and calls cancel
154    pub fn spawn_terminator(&self) -> std::thread::JoinHandle<Result<(), std::io::Error>> {
155        let this = self.clone();
156        std::thread::spawn(move || {
157            let mut signals = signal_hook::iterator::Signals::new([
158                signal_hook::consts::SIGTERM,
159                signal_hook::consts::SIGINT,
160            ])
161            .inspect_err(|_| {
162                this.cancel();
163            })?;
164
165            for _ in &mut signals {
166                this.cancel();
167            }
168
169            Ok(())
170        })
171    }
172
173    /// Create an already cancelled token
174    pub fn ready() -> Self {
175        Self {
176            inner: Arc::new(WaitTokenRaw {
177                state: true.into(),
178                parent: None,
179                future: Some(tokio::sync::Notify::new()),
180            }),
181        }
182    }
183
184    fn wake(&self) {
185        if let Some(parent) = &self.inner.parent {
186            parent.wake();
187            return;
188        }
189        if let Some(future) = &self.inner.future {
190            future.notify_waiters();
191        }
192    }
193
194    fn notified(&self) -> tokio::sync::futures::Notified<'_> {
195        self.inner.notified()
196    }
197
198    /// Set state to 'cancelled'. Futures waiting for token will complete and streams terminated.
199    pub fn cancel(&self) -> CancelState {
200        if self.inner.state.swap(true, atomic::Ordering::Release) {
201            return CancelState::AlreadyCancelled;
202        }
203        self.wake();
204        CancelState::Cancelled
205    }
206
207    /// Set state to 'not cancelled'
208    pub fn reset(&self) {
209        self.inner.state.store(false, atomic::Ordering::Release);
210    }
211
212    pub fn is_cancelled(&self) -> bool {
213        self.inner.is_cancelled()
214    }
215
216    pub fn cancelled(&self) -> WaitTokenCancelled {
217        WaitTokenCancelled {
218            token: self.clone(),
219            future: Box::pin(unsafe {
220                std::mem::transmute::<
221                    tokio::sync::futures::Notified<'_>,
222                    tokio::sync::futures::Notified<'_>,
223                >(self.notified())
224            }),
225        }
226    }
227
228    /// Make a stream that will emit items until token is cancelled
229    pub fn until_cancel(
230        &self,
231    ) -> futures::stream::TakeUntil<futures::stream::Repeat<()>, WaitTokenCancelled> {
232        futures::stream::repeat(()).take_until(self.cancelled())
233    }
234
235    pub fn on_cancel(&self) -> futures::stream::Once<WaitTokenCancelled> {
236        futures::stream::once(self.cancelled())
237    }
238    pub async fn on_cancel_then<Fut: Future + Send>(self, fut: Fut) -> Fut::Output {
239        self.cancelled().then(|_| fut).await
240    }
241    /// Make an object that will wait for delay every iteration and return false if token is cancelled.
242    /// Common usage is with while loop.
243    ///
244    /// Example:
245    /// ```rust
246    /// let mut repeater = cancellation.repeat_until_cancel(Duration::from_secs(1));
247    /// while repeater.next().await {
248    ///     do_something();
249    /// }
250    /// ```
251    pub fn repeat_until_cancel(&self, frq: Duration) -> WaitTokenRepeat<Self> {
252        WaitTokenRepeat {
253            tk: self.clone(),
254            frq,
255        }
256    }
257
258    /// Run future. Future will be aborted when token is cancelled
259    pub async fn run_fn<Fut: Future>(&self, fut: Fut) -> Option<Fut::Output> {
260        tokio::select! {
261            a = fut => Some(a),
262            _ = self.cancelled() => None,
263        }
264    }
265
266    /// Create a sub-token that will cancel if current token cancelled. <br/>
267    /// Cancel on child token will not cancel parent
268    pub fn make_child_token(&self) -> Self {
269        Self {
270            inner: Arc::new(WaitTokenRaw {
271                state: false.into(),
272                parent: Some(self.clone()),
273                future: None,
274            }),
275        }
276    }
277
278    /// Create a guard that will cancel wait token on drop
279    pub fn guard(&self) -> WaitTokenGuard {
280        WaitTokenGuard { tk: self.clone() }
281    }
282}
283
284impl Default for WaitToken {
285    fn default() -> Self {
286        Self::new()
287    }
288}
289
290pub struct WaitTokenCancelled {
291    token: WaitToken,
292    future: Pin<Box<tokio::sync::futures::Notified<'static>>>,
293}
294
295impl Future for WaitTokenCancelled {
296    type Output = ();
297
298    fn poll(
299        mut self: std::pin::Pin<&mut Self>,
300        cx: &mut std::task::Context<'_>,
301    ) -> std::task::Poll<Self::Output> {
302        loop {
303            if self.token.is_cancelled() {
304                return std::task::Poll::Ready(());
305            }
306
307            if self.future.poll_unpin(cx).is_pending() {
308                return std::task::Poll::Pending;
309            }
310
311            if self.token.is_cancelled() {
312                return std::task::Poll::Ready(());
313            }
314
315            let future = unsafe {
316                std::mem::transmute::<
317                    tokio::sync::futures::Notified<'_>,
318                    tokio::sync::futures::Notified<'_>,
319                >(self.token.notified())
320            };
321            self.future.set(future);
322        }
323    }
324}