killswitch_std/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
#![deny(missing_docs)]

//! A simple implementation of a kill switch, using only the standard library
//!
//! # Example
//! ```
//!# tokio_test::block_on( async {
//! use std::time::Duration;
//! use killswitch_std::KillSwitch;
//!
//! // Create a kill switch
//! let kill = KillSwitch::default();
//!
//! println!("Is kill switch set? {}", kill);
//!
//! // Now make a couple of clones and check
//! let k1 = kill.watcher();
//! let t1 = tokio::spawn(async move {
//!     let duration = Duration::from_secs(2);
//!     for _ in 0..5 {
//!         tokio::time::sleep(duration).await;
//!         println!("Kill switch on thread 1: {}", k1);
//!     }
//!     println!("Thread 1 wrapping up");
//! });
//!
//! // Now make a couple of clones and check
//! let k2 = kill.watcher();
//! let t2 = tokio::spawn(async move {
//!     let duration = Duration::from_secs(2);
//!     for _ in 0..5 {
//!         tokio::time::sleep(duration).await;
//!         println!("Kill switch on thread 2: {}", k2);
//!     }
//!     println!("Thread 2 wrapping up");
//! });
//!
//! let duration = Duration::from_secs(7);
//! tokio::time::sleep(duration).await;
//! println!("Flipping kill switch on main thread");
//! let _ = kill.kill();
//!
//! let _ = tokio::join!(t1, t2);
//!
//! println!("All threads finished");
//!# })
//! ```

use std::{
    fmt::Display,
    sync::{
        atomic::{AtomicBool, Ordering::Relaxed},
        Arc,
    },
};

/// Convenience type which wraps a [`AtomicBool`].
/// Initially, `is_alive()` will return `true`. The value can be cloned across threads, and once it
/// has been `kill()`ed, then all of the clones will return `false` from `is_alive()`.
#[derive(Clone)]
pub struct KillSwitch {
    switch: Arc<AtomicBool>,
}

/// Derived from a [`KillSwitch`], allows to check if the kill switch is still alive, but cannot
/// activate it. This may be useful in separating out a thread which is only watching the value of
/// the kill switch.
#[derive(Clone)]
pub struct KillSwitchWatcher {
    switch: Arc<AtomicBool>,
}

impl KillSwitchWatcher {
    /// Check if the kill switch has been flipped. Before flipping will return `true`, and
    /// afterwards will return `false`
    pub fn is_alive(&self) -> bool {
        self.switch.load(Relaxed)
    }
}
impl KillSwitch {
    /// Check if the kill switch has been flipped. Before flipping will return `true`, and
    /// afterwards will return `false`
    pub fn is_alive(&self) -> bool {
        self.switch.load(Relaxed)
    }

    /// Flip the kill switch (will cause `is_alive()` to return `false`
    pub fn kill(&self) -> Result<(), KillSwitchErr> {
        match self.is_alive() {
            true => {
                self.switch.store(false, Relaxed);
                Ok(())
            }
            false => Err(KillSwitchErr::AlreadyKilled),
        }
    }

    /// Produce a kill switch which can only watch the value, but cannot flip the switch
    pub fn watcher(&self) -> KillSwitchWatcher {
        KillSwitchWatcher {
            switch: self.switch.clone(),
        }
    }
}

impl Default for KillSwitch {
    fn default() -> Self {
        Self {
            switch: Arc::new(AtomicBool::new(true)),
        }
    }
}

impl Display for KillSwitchWatcher {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self.is_alive() {
                true => "alive",
                false => "killed",
            }
        )
    }
}

impl Display for KillSwitch {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}",
            match self.is_alive() {
                true => "alive",
                false => "killed",
            }
        )
    }
}

/// General error type for a [`KillSwitch`]
#[derive(Debug, Clone)]
pub enum KillSwitchErr {
    /// Kill switch has already been flipped
    AlreadyKilled,
}

impl std::error::Error for KillSwitchErr {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        None
    }
}

impl std::fmt::Display for KillSwitchErr {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            KillSwitchErr::AlreadyKilled => write!(f, "kill switch already killed"),
        }
    }
}