Skip to main content

lucy/
cancellation.rs

1use std::sync::atomic::{AtomicU8, Ordering};
2use std::sync::Arc;
3
4const ACTIVE: u8 = 0;
5const CANCELLED: u8 = 1;
6const COMPLETED: u8 = 2;
7
8/// A small cancellation signal shared by the interactive frontend and the
9/// provider/command work running for the active turn.
10#[derive(Clone, Debug)]
11pub(crate) struct CancellationToken {
12    state: Arc<AtomicU8>,
13}
14
15impl CancellationToken {
16    pub(crate) fn new() -> Self {
17        Self {
18            state: Arc::new(AtomicU8::new(ACTIVE)),
19        }
20    }
21
22    /// Request cancellation. A completed turn cannot be retroactively changed
23    /// into an interruption.
24    pub(crate) fn cancel(&self) -> bool {
25        self.state
26            .compare_exchange(ACTIVE, CANCELLED, Ordering::AcqRel, Ordering::Acquire)
27            .is_ok()
28    }
29
30    pub(crate) fn is_cancelled(&self) -> bool {
31        self.state.load(Ordering::Acquire) == CANCELLED
32    }
33
34    /// Atomically choose normal completion over a concurrent cancellation.
35    pub(crate) fn try_complete(&self) -> bool {
36        self.state
37            .compare_exchange(ACTIVE, COMPLETED, Ordering::AcqRel, Ordering::Acquire)
38            .is_ok()
39    }
40}
41
42impl Default for CancellationToken {
43    fn default() -> Self {
44        Self::new()
45    }
46}
47
48#[cfg(test)]
49mod tests {
50    use super::*;
51
52    #[test]
53    fn completion_and_cancellation_have_one_linearization_winner() {
54        let completed = CancellationToken::new();
55        assert!(completed.try_complete());
56        completed.cancel();
57        assert!(!completed.is_cancelled());
58        assert!(!completed.try_complete());
59
60        let canceled = CancellationToken::new();
61        canceled.cancel();
62        assert!(canceled.is_cancelled());
63        assert!(!canceled.try_complete());
64    }
65}