zeppelin_embed/lifecycle/
cancel.rs1use std::sync::Arc;
12use std::sync::atomic::{AtomicU8, Ordering};
13use std::time::{Duration, Instant};
14
15use crate::scan::ScanError;
16
17use super::{SnapshotLease, StoreError};
18
19const ACTIVE: u8 = 0;
20const CANCELLED: u8 = 1;
21const TIMED_OUT: u8 = 2;
22
23#[derive(Clone, Debug)]
25pub struct CancelToken {
26 state: Arc<AtomicU8>,
27}
28
29impl CancelToken {
30 #[must_use]
32 pub fn new() -> Self {
33 Self {
34 state: Arc::new(AtomicU8::new(ACTIVE)),
35 }
36 }
37
38 pub fn cancel(&self) {
40 let _ =
41 self.state
42 .compare_exchange(ACTIVE, CANCELLED, Ordering::Relaxed, Ordering::Relaxed);
43 }
44
45 pub(crate) fn is_cancelled(&self) -> bool {
46 self.state.load(Ordering::Relaxed) == CANCELLED
47 }
48}
49
50impl Default for CancelToken {
51 fn default() -> Self {
52 Self::new()
53 }
54}
55
56#[derive(Clone)]
58pub struct Deadline {
59 at: Instant,
60 clock: Arc<dyn super::MonotonicClock>,
61 state: Arc<AtomicU8>,
62}
63
64impl std::fmt::Debug for Deadline {
65 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 formatter
67 .debug_struct("Deadline")
68 .field("at", &self.at)
69 .field("state", &self.state.load(Ordering::Relaxed))
70 .finish_non_exhaustive()
71 }
72}
73
74impl Deadline {
75 pub fn after(duration: Duration) -> Result<Self, DeadlineError> {
82 Self::after_with_clock(duration, Arc::new(super::SystemMonotonicClock))
83 }
84
85 #[cfg(any(test, feature = "test-seams"))]
90 pub fn after_with_test_clock(
91 duration: Duration,
92 clock: Arc<dyn super::MonotonicClock>,
93 ) -> Result<Self, DeadlineError> {
94 Self::after_with_clock(duration, clock)
95 }
96
97 pub(crate) fn after_with_clock(
98 duration: Duration,
99 clock: Arc<dyn super::MonotonicClock>,
100 ) -> Result<Self, DeadlineError> {
101 let at = clock
102 .now()
103 .checked_add(duration)
104 .ok_or(DeadlineError::OutOfRange)?;
105 Ok(Self {
106 at,
107 clock,
108 state: Arc::new(AtomicU8::new(ACTIVE)),
109 })
110 }
111}
112
113#[derive(Clone, Copy, Debug, Eq, PartialEq)]
115pub enum DeadlineError {
116 OutOfRange,
118}
119
120impl std::fmt::Display for DeadlineError {
121 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
122 match self {
123 Self::OutOfRange => formatter.write_str("query deadline is outside Instant's range"),
124 }
125 }
126}
127
128impl std::error::Error for DeadlineError {}
129
130#[derive(Clone, Debug)]
132pub enum QueryControl {
133 Deadline(Deadline),
135 Cancel(CancelToken),
137}
138
139#[derive(Debug)]
141pub enum QueryError {
142 Timeout {
144 partial: bool,
146 },
147 Cancelled {
149 partial: bool,
151 },
152 ReadCancelled {
154 partial: bool,
156 },
157 Store(StoreError),
159 Scan(ScanError),
161 Graph(crate::graph::search::GraphSearchError),
163}
164
165impl std::fmt::Display for QueryError {
166 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
167 match self {
168 Self::Timeout { partial } => {
169 write!(formatter, "query deadline expired (partial={partial})")
170 }
171 Self::Cancelled { partial } => {
172 write!(formatter, "query was cancelled (partial={partial})")
173 }
174 Self::ReadCancelled { partial } => {
175 write!(formatter, "store close cancelled query (partial={partial})")
176 }
177 Self::Store(error) => error.fmt(formatter),
178 Self::Scan(error) => error.fmt(formatter),
179 Self::Graph(error) => error.fmt(formatter),
180 }
181 }
182}
183
184impl std::error::Error for QueryError {
185 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
186 match self {
187 Self::Store(error) => Some(error),
188 Self::Scan(error) => Some(error),
189 Self::Graph(error) => Some(error),
190 Self::Timeout { .. } | Self::Cancelled { .. } | Self::ReadCancelled { .. } => None,
191 }
192 }
193}
194
195impl QueryControl {
196 pub fn checkpoint(&self) -> Result<(), QueryError> {
207 if self
208 .deadline()
209 .zip(self.now())
210 .is_some_and(|(deadline, now)| now >= deadline)
211 {
212 self.mark_timed_out();
213 }
214 self.error().map_or(Ok(()), Err)
215 }
216
217 pub(crate) fn with_clock(mut self, clock: Arc<dyn super::MonotonicClock>) -> Self {
218 if let Self::Deadline(deadline) = &mut self {
219 deadline.clock = clock;
220 }
221 self
222 }
223
224 pub(crate) fn deadline(&self) -> Option<Instant> {
225 match self {
226 Self::Deadline(deadline) => Some(deadline.at),
227 Self::Cancel(_) => None,
228 }
229 }
230
231 pub(crate) fn now(&self) -> Option<Instant> {
232 match self {
233 Self::Deadline(deadline) => Some(deadline.clock.now()),
234 Self::Cancel(_) => None,
235 }
236 }
237
238 pub(crate) fn state(&self) -> &Arc<AtomicU8> {
239 match self {
240 Self::Deadline(deadline) => &deadline.state,
241 Self::Cancel(token) => &token.state,
242 }
243 }
244
245 pub(crate) fn mark_timed_out(&self) {
246 let _ =
247 self.state()
248 .compare_exchange(ACTIVE, TIMED_OUT, Ordering::Relaxed, Ordering::Relaxed);
249 }
250
251 pub(crate) fn error(&self) -> Option<QueryError> {
252 match self.state().load(Ordering::Relaxed) {
253 CANCELLED => Some(QueryError::Cancelled { partial: false }),
254 TIMED_OUT => Some(QueryError::Timeout { partial: false }),
255 _ => None,
256 }
257 }
258}
259
260pub struct QueryCancellation<'a> {
262 control: &'a QueryControl,
263 lease: &'a SnapshotLease,
264}
265
266impl<'a> QueryCancellation<'a> {
267 pub(crate) fn cache_lock<'lock, T>(
268 &self,
269 mutex: &'lock std::sync::Mutex<T>,
270 component: &'static str,
271 ) -> Result<std::sync::MutexGuard<'lock, T>, QueryError> {
272 self.check_graph().map_err(QueryError::Scan)?;
273 let guard = loop {
274 match mutex.try_lock() {
275 Ok(guard) => break guard,
276 Err(std::sync::TryLockError::Poisoned(_)) => {
277 return Err(QueryError::Store(StoreError::Synchronization { component }));
278 }
279 Err(std::sync::TryLockError::WouldBlock) => {
280 self.check_graph().map_err(QueryError::Scan)?;
281 std::thread::park_timeout(Duration::from_millis(1));
282 }
283 }
284 };
285 self.check_graph().map_err(QueryError::Scan)?;
286 Ok(guard)
287 }
288
289 #[must_use]
291 pub const fn new(control: &'a QueryControl, lease: &'a SnapshotLease) -> Self {
292 Self { control, lease }
293 }
294
295 pub(crate) fn check(&self) -> Result<(), ScanError> {
296 if self.lease.is_cancelled() {
297 return Err(ScanError::ReadCancelled { partial: false });
298 }
299 match self.control.state().load(Ordering::Relaxed) {
300 CANCELLED => Err(ScanError::Cancelled { partial: false }),
301 TIMED_OUT => Err(ScanError::Timeout { partial: false }),
302 _ => Ok(()),
303 }
304 }
305
306 pub(crate) fn check_graph(&self) -> Result<(), ScanError> {
307 if self
308 .control
309 .deadline()
310 .zip(self.control.now())
311 .is_some_and(|(deadline, now)| now >= deadline)
312 {
313 self.control.mark_timed_out();
314 }
315 self.check()
316 }
317
318 #[cfg(any(test, feature = "test-seams"))]
319 pub(crate) fn cancel_token_for_test(&self) -> Option<CancelToken> {
320 match self.control {
321 QueryControl::Cancel(token) => Some(token.clone()),
322 QueryControl::Deadline(_) => None,
323 }
324 }
325}