ferroui_base/threading/
cancellation_token.rs1use std::sync::atomic::{AtomicBool, Ordering};
8use std::sync::{Arc, Mutex, PoisonError};
9
10type Callback = Box<dyn FnOnce() + Send>;
11
12struct Inner {
13 canceled: AtomicBool,
14 callbacks: Mutex<Callbacks>,
15}
16
17#[derive(Default)]
18struct Callbacks {
19 next_id: u64,
20 entries: Vec<(u64, Callback)>,
21}
22
23#[derive(Clone)]
26pub struct CancellationTokenSource {
27 inner: Arc<Inner>,
28}
29
30impl Default for CancellationTokenSource {
31 fn default() -> Self {
32 Self::new()
33 }
34}
35
36impl CancellationTokenSource {
37 pub fn new() -> Self {
38 Self {
39 inner: Arc::new(Inner { canceled: AtomicBool::new(false), callbacks: Mutex::new(Callbacks::default()) }),
40 }
41 }
42
43 pub fn token(&self) -> CancellationToken {
45 CancellationToken { inner: Some(self.inner.clone()) }
46 }
47
48 pub fn is_cancellation_requested(&self) -> bool {
49 self.inner.canceled.load(Ordering::SeqCst)
50 }
51
52 pub fn cancel(&self) {
55 if self.inner.canceled.swap(true, Ordering::SeqCst) {
56 return;
57 }
58 let entries = {
59 let mut callbacks = self.inner.callbacks.lock().unwrap_or_else(PoisonError::into_inner);
60 std::mem::take(&mut callbacks.entries)
61 };
62 for (_, callback) in entries {
63 callback();
64 }
65 }
66}
67
68#[derive(Clone, Default)]
70pub struct CancellationToken {
71 inner: Option<Arc<Inner>>,
72}
73
74impl CancellationToken {
75 pub const fn none() -> Self {
77 Self { inner: None }
78 }
79
80 pub fn can_be_canceled(&self) -> bool {
82 self.inner.is_some()
83 }
84
85 pub fn is_cancellation_requested(&self) -> bool {
86 self.inner.as_ref().is_some_and(|inner| inner.canceled.load(Ordering::SeqCst))
87 }
88
89 pub fn register(&self, callback: impl FnOnce() + Send + 'static) -> CancellationTokenRegistration {
92 let Some(inner) = &self.inner else {
93 return CancellationTokenRegistration { inner: None, id: 0 };
94 };
95 {
96 let mut callbacks = inner.callbacks.lock().unwrap_or_else(PoisonError::into_inner);
97 if !inner.canceled.load(Ordering::SeqCst) {
100 callbacks.next_id += 1;
101 let id = callbacks.next_id;
102 callbacks.entries.push((id, Box::new(callback)));
103 return CancellationTokenRegistration { inner: Some(inner.clone()), id };
104 }
105 }
106 callback();
107 CancellationTokenRegistration { inner: None, id: 0 }
108 }
109}
110
111pub struct CancellationTokenRegistration {
113 inner: Option<Arc<Inner>>,
114 id: u64,
115}
116
117impl CancellationTokenRegistration {
118 pub fn dispose(&self) {
120 if let Some(inner) = &self.inner {
121 let removed = {
122 let mut callbacks = inner.callbacks.lock().unwrap_or_else(PoisonError::into_inner);
123 callbacks.entries.iter().position(|(id, _)| *id == self.id).map(|i| callbacks.entries.remove(i))
124 };
125 drop(removed);
126 }
127 }
128}
129
130#[cfg(test)]
131mod tests {
132 use super::*;
133 use std::sync::atomic::AtomicUsize;
134
135 #[test]
136 fn none_cannot_be_canceled() {
137 let token = CancellationToken::none();
138 assert!(!token.can_be_canceled());
139 assert!(!token.is_cancellation_requested());
140 }
141
142 #[test]
143 fn cancel_runs_callbacks_once() {
144 let count = Arc::new(AtomicUsize::new(0));
145 let source = CancellationTokenSource::new();
146 let token = source.token();
147 let c = count.clone();
148 token.register(move || {
149 c.fetch_add(1, Ordering::SeqCst);
150 });
151 let c = count.clone();
152 let removed = token.register(move || {
153 c.fetch_add(10, Ordering::SeqCst);
154 });
155 removed.dispose();
156 source.cancel();
157 source.cancel();
158 assert!(token.is_cancellation_requested());
159 assert_eq!(count.load(Ordering::SeqCst), 1);
160
161 let c = count.clone();
162 token.register(move || {
163 c.fetch_add(100, Ordering::SeqCst);
164 });
165 assert_eq!(count.load(Ordering::SeqCst), 101);
166 }
167}