random_image_server/
termination.rs

1use tokio::signal::unix::signal;
2use tokio::sync::broadcast;
3
4#[derive(Debug, Clone, Copy, PartialEq, Eq)]
5pub enum Interrupted {
6    OsSigInt,
7    OsSigQuit,
8    OsSigTerm,
9    UserInt,
10}
11
12#[derive(Debug, Clone)]
13pub struct Terminator {
14    interrupt_tx: broadcast::Sender<Interrupted>,
15}
16
17impl Terminator {
18    #[must_use]
19    pub const fn new(interrupt_tx: broadcast::Sender<Interrupted>) -> Self {
20        Self { interrupt_tx }
21    }
22
23    /// Send an interrupt signal to the application.
24    ///
25    /// # Errors
26    ///
27    /// Fails if the interrupt signal cannot be sent (e.g. the receiver has been dropped)
28    pub fn terminate(&mut self, interrupted: Interrupted) -> anyhow::Result<()> {
29        self.interrupt_tx.send(interrupted)?;
30
31        Ok(())
32    }
33}
34
35async fn terminate_by_unix_signal(mut terminator: Terminator) {
36    let mut interrupt_signal = signal(tokio::signal::unix::SignalKind::interrupt())
37        .expect("failed to create interrupt signal stream");
38    let mut term_signal = signal(tokio::signal::unix::SignalKind::terminate())
39        .expect("failed to create terminate signal stream");
40    let mut quit_signal = signal(tokio::signal::unix::SignalKind::quit())
41        .expect("failed to create quit signal stream");
42
43    tokio::select! {
44        _ = interrupt_signal.recv() => {
45            terminator
46                .terminate(Interrupted::OsSigInt)
47                .expect("failed to send interrupt signal");
48        }
49        _ = term_signal.recv() => {
50            terminator
51                .terminate(Interrupted::OsSigTerm)
52                .expect("failed to send terminate signal");
53        }
54        _ = quit_signal.recv() => {
55            terminator
56                .terminate(Interrupted::OsSigQuit)
57                .expect("failed to send quit signal");
58        }
59    }
60}
61
62// create a broadcast channel for retrieving the application kill signal
63#[allow(clippy::module_name_repetitions)]
64#[must_use]
65pub fn create_termination() -> (Terminator, broadcast::Receiver<Interrupted>) {
66    let (tx, rx) = broadcast::channel(1);
67    let terminator = Terminator::new(tx);
68
69    tokio::spawn(terminate_by_unix_signal(terminator.clone()));
70
71    (terminator, rx)
72}
73
74#[cfg(test)]
75mod test {
76    use std::time::Duration;
77
78    use super::*;
79    use rstest::rstest;
80
81    #[rstest]
82    #[timeout(Duration::from_secs(1))]
83    #[tokio::test]
84    async fn test_terminate() {
85        let (mut terminator, mut rx) = create_termination();
86
87        terminator
88            .terminate(Interrupted::UserInt)
89            .expect("failed to send interrupt signal");
90
91        assert_eq!(rx.recv().await, Ok(Interrupted::UserInt));
92    }
93}