Skip to main content

concinnity_engine/
shutdown.rs

1//! Cooperative shutdown signal shared across threads. Clones observe the same
2//! flag: any clone's `cancel()` is visible to every other clone's
3//! `is_cancelled()`. Cancellation is one-way and sticky.
4
5use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, Ordering};
7
8/// Cloneable one-shot cancellation flag. The run loop polls `is_cancelled`
9/// each tick; signal handlers and debug servers call `cancel` to stop it.
10#[derive(Debug, Clone, Default)]
11pub struct ShutdownToken(Arc<AtomicBool>);
12
13impl ShutdownToken {
14    /// A fresh, uncancelled token.
15    pub fn new() -> Self {
16        Self::default()
17    }
18
19    /// Signal shutdown to every clone of this token.
20    pub fn cancel(&self) {
21        self.0.store(true, Ordering::Release);
22    }
23
24    /// Whether any clone has signalled shutdown.
25    pub fn is_cancelled(&self) -> bool {
26        self.0.load(Ordering::Acquire)
27    }
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33
34    #[test]
35    fn starts_uncancelled_and_sticks_once_cancelled() {
36        let token = ShutdownToken::new();
37        assert!(!token.is_cancelled());
38        token.cancel();
39        assert!(token.is_cancelled());
40        token.cancel();
41        assert!(token.is_cancelled());
42    }
43
44    #[test]
45    fn clones_share_the_flag() {
46        let token = ShutdownToken::new();
47        let clone = token.clone();
48        clone.cancel();
49        assert!(token.is_cancelled());
50    }
51
52    #[test]
53    fn cancel_crosses_threads() {
54        let token = ShutdownToken::new();
55        let clone = token.clone();
56        std::thread::spawn(move || clone.cancel())
57            .join()
58            .expect("cancel thread panicked");
59        assert!(token.is_cancelled());
60    }
61}