bloq_graph/
cancellation.rs1use std::cell::RefCell;
4use std::sync::{
5 Arc,
6 atomic::{AtomicBool, Ordering},
7};
8
9#[derive(Debug, Clone, Copy, thiserror::Error)]
12#[error("computation cancelled")]
13pub struct ComputationCancelled;
14
15#[derive(Debug, Clone, Default)]
21pub struct CancellationToken(Arc<AtomicBool>);
22
23thread_local! {
24 static ACTIVE: RefCell<Option<CancellationToken>> = const { RefCell::new(None) };
25}
26
27impl CancellationToken {
28 pub fn new() -> Self {
30 Self::default()
31 }
32
33 pub fn cancel(&self) {
35 self.0.store(true, Ordering::Relaxed);
36 }
37
38 pub fn is_cancelled(&self) -> bool {
40 self.0.load(Ordering::Relaxed)
41 }
42
43 pub fn check(&self) -> Result<(), ComputationCancelled> {
49 if self.is_cancelled() {
50 Err(ComputationCancelled)
51 } else {
52 Ok(())
53 }
54 }
55
56 pub fn run<T, E: From<ComputationCancelled>>(
64 &self,
65 f: impl FnOnce() -> Result<T, E>,
66 ) -> Result<T, E> {
67 self.check()?;
68 self.scope(|| {
69 let result = f();
70 self.check()?;
71 result
72 })
73 }
74
75 pub(crate) fn current() -> Option<Self> {
76 ACTIVE.with(|active| active.borrow().clone())
77 }
78
79 pub(crate) fn scope<T>(&self, f: impl FnOnce() -> T) -> T {
80 struct Restore(Option<CancellationToken>);
81 impl Drop for Restore {
82 fn drop(&mut self) {
83 ACTIVE.with(|active| {
84 active.replace(self.0.take());
85 });
86 }
87 }
88 let previous = ACTIVE.with(|active| active.replace(Some(self.clone())));
89 let _restore = Restore(previous);
90 f()
91 }
92}
93
94#[cfg(test)]
95mod tests {
96 use super::*;
97
98 #[test]
99 fn scoped_workers_observe_cancellation_without_leaking_into_the_next_request() {
100 for _ in 0..2 {
101 let token = CancellationToken::new();
102 token.scope(|| {
103 let workers =
104 crate::map_jobs(&[1, 2], std::num::NonZeroUsize::new(2).unwrap(), |_| {
105 let active = CancellationToken::current().expect("worker inherits request");
106 active.cancel();
107 active.check()
108 });
109 assert!(workers.iter().all(Result::is_err));
110 });
111 assert!(CancellationToken::current().is_none());
112 }
113 CancellationToken::new()
114 .run(|| Ok::<_, ComputationCancelled>(()))
115 .unwrap();
116 }
117}