leviath_runtime/
cancel.rs1use std::sync::Arc;
18use std::sync::atomic::{AtomicBool, Ordering};
19
20use tokio::sync::Notify;
21
22#[derive(Default)]
23struct Inner {
24 cancelled: AtomicBool,
25 notify: Notify,
26}
27
28#[derive(Clone, Default)]
32pub struct CancelToken {
33 inner: Arc<Inner>,
34}
35
36impl CancelToken {
37 pub fn new() -> Self {
39 Self::default()
40 }
41
42 pub fn cancel(&self) {
44 self.inner.cancelled.store(true, Ordering::SeqCst);
45 self.inner.notify.notify_waiters();
46 }
47
48 pub fn is_cancelled(&self) -> bool {
50 self.inner.cancelled.load(Ordering::SeqCst)
51 }
52
53 pub async fn cancelled(&self) {
60 let notified = self.inner.notify.notified();
61 tokio::pin!(notified);
62 notified.as_mut().enable();
63 if self.is_cancelled() {
64 return;
65 }
66 notified.await;
67 }
68}
69
70impl std::fmt::Debug for CancelToken {
71 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
72 f.debug_struct("CancelToken")
73 .field("cancelled", &self.is_cancelled())
74 .finish()
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81 use std::time::Duration;
82
83 #[tokio::test]
84 async fn cancelled_resolves_when_the_token_fires() {
85 let token = CancelToken::new();
86 assert!(!token.is_cancelled());
87
88 let waiter = tokio::spawn({
89 let token = token.clone();
90 async move { token.cancelled().await }
91 });
92 tokio::task::yield_now().await;
94 token.cancel();
95
96 tokio::time::timeout(Duration::from_secs(5), waiter)
97 .await
98 .expect("a fired token wakes its waiter")
99 .unwrap();
100 assert!(token.is_cancelled());
101 }
102
103 #[tokio::test]
104 async fn cancelled_returns_immediately_for_an_already_fired_token() {
105 let token = CancelToken::new();
106 token.cancel();
107 tokio::time::timeout(Duration::from_secs(5), token.cancelled())
108 .await
109 .expect("no wait for a token that already fired");
110 }
111
112 #[tokio::test(flavor = "multi_thread", worker_threads = 2)]
118 async fn a_cancel_concurrent_with_the_wait_is_observed() {
119 for _ in 0..500 {
120 let token = CancelToken::new();
121 let firing = tokio::spawn({
122 let token = token.clone();
123 async move { token.cancel() }
124 });
125 tokio::time::timeout(Duration::from_secs(5), token.cancelled())
126 .await
127 .expect("the cancel was observed");
128 firing.await.unwrap();
129 }
130 }
131
132 #[tokio::test]
133 async fn cancel_is_idempotent_and_wakes_every_clone() {
134 let token = CancelToken::new();
135 let waiters: Vec<_> = (0..3)
136 .map(|_| {
137 let token = token.clone();
138 tokio::spawn(async move { token.cancelled().await })
139 })
140 .collect();
141 tokio::task::yield_now().await;
142 token.cancel();
143 token.cancel(); for waiter in waiters {
146 tokio::time::timeout(Duration::from_secs(5), waiter)
147 .await
148 .expect("every clone observes the cancel")
149 .unwrap();
150 }
151 }
152
153 #[test]
154 fn debug_reports_the_state() {
155 let token = CancelToken::new();
156 assert!(format!("{token:?}").contains("cancelled: false"));
157 token.cancel();
158 assert!(format!("{token:?}").contains("cancelled: true"));
159 }
160}