Skip to main content

uxum/
signal.rs

1//! UNIX signal handling routines.
2
3use thiserror::Error;
4use tokio::signal::unix;
5use tracing::{info, warn};
6
7use crate::errors::IoError;
8
9/// Signal handling error.
10#[derive(Debug, Error)]
11#[non_exhaustive]
12pub enum SignalError {
13    /// Unable to register signal handler.
14    #[error("Unable to register signal handler: {0}")]
15    Register(IoError),
16}
17
18/// Register signal handler.
19///
20/// # Errors
21///
22/// Returns `Err` if internal call to [`unix::signal`] fails.
23fn register(kind: unix::SignalKind) -> Result<unix::Signal, SignalError> {
24    unix::signal(kind).map_err(|err| SignalError::Register(err.into()))
25}
26
27/// Unified listener for all handled signals.
28pub struct SignalStream {
29    /// Signal listener for SIGTERM.
30    sig_term: unix::Signal,
31    /// Signal listener for SIGINT.
32    sig_int: unix::Signal,
33    /// Signal listener for SIGQUIT.
34    sig_quit: unix::Signal,
35    /// Signal listener for SIGHUP.
36    sig_hup: unix::Signal,
37    /// Signal listener for SIGUSR1.
38    sig_usr1: unix::Signal,
39    /// Signal listener for SIGUSR2.
40    sig_usr2: unix::Signal,
41}
42
43/// Signal type.
44#[derive(Clone, Copy, Debug, PartialEq, Eq)]
45#[non_exhaustive]
46pub enum Signal {
47    /// SIGTERM.
48    Terminate,
49    /// SIGINT.
50    Interrupt,
51    /// SIGQUIT.
52    Quit,
53    /// SIGHUP.
54    HangUp,
55    /// SIGUSR1.
56    UserDefined1,
57    /// SIGUSR2.
58    UserDefined2,
59}
60
61impl Signal {
62    /// Name of a signal, as written in UNIX manual pages.
63    #[must_use]
64    pub fn name(&self) -> &'static str {
65        match self {
66            Self::Terminate => "SIGTERM",
67            Self::Interrupt => "SIGINT",
68            Self::Quit => "SIGQUIT",
69            Self::HangUp => "SIGHUP",
70            Self::UserDefined1 => "SIGUSR1",
71            Self::UserDefined2 => "SIGUSR2",
72        }
73    }
74
75    /// Whether a given signal should result in application shutting down.
76    #[must_use]
77    pub fn is_shutdown(&self) -> bool {
78        matches!(self, Self::Terminate | Self::Interrupt | Self::Quit)
79    }
80}
81
82impl SignalStream {
83    /// Create new [`SignalStream`].
84    ///
85    /// Automatically registers all signal handlers.
86    ///
87    /// # Errors
88    ///
89    /// Returns `Err` if some signal handler failed to register.
90    pub fn new() -> Result<Self, SignalError> {
91        // TODO: cancellation token?
92        let sig_term = register(unix::SignalKind::terminate())?;
93        let sig_int = register(unix::SignalKind::interrupt())?;
94        let sig_quit = register(unix::SignalKind::quit())?;
95        let sig_hup = register(unix::SignalKind::hangup())?;
96        let sig_usr1 = register(unix::SignalKind::user_defined1())?;
97        let sig_usr2 = register(unix::SignalKind::user_defined2())?;
98
99        Ok(Self {
100            sig_term,
101            sig_int,
102            sig_quit,
103            sig_hup,
104            sig_usr1,
105            sig_usr2,
106        })
107    }
108
109    /// Wait for next received signal.
110    ///
111    /// # Errors
112    ///
113    /// Returns `Err` if some signal handler failed to register.
114    pub async fn next(&mut self) -> Result<Signal, SignalError> {
115        macro_rules! sig_recv {
116            ($name:literal, $value:ident) => {
117                info!(kind = $name, "received signal");
118                return Ok(Signal::$value);
119            };
120        }
121        macro_rules! sig_restart {
122            ($name:literal, $sig:ident, $create:ident) => {
123                warn!(kind = $name, "signal handler exited, restarting");
124                self.$sig = register(unix::SignalKind::$create())?;
125                continue;
126            };
127        }
128        loop {
129            tokio::select! {
130                ret = self.sig_term.recv() => match ret {
131                    Some(_) => { sig_recv!("SIGTERM", Terminate); }
132                    None => { sig_restart!("SIGTERM", sig_term, terminate); }
133                },
134                ret = self.sig_int.recv() => match ret {
135                    Some(_) => { sig_recv!("SIGINT", Interrupt); }
136                    None => { sig_restart!("SIGINT", sig_int, interrupt); }
137                },
138                ret = self.sig_quit.recv() => match ret {
139                    Some(_) => { sig_recv!("SIGQUIT", Quit); }
140                    None => { sig_restart!("SIGQUIT", sig_quit, quit); }
141                },
142                ret = self.sig_hup.recv() => match ret {
143                    Some(_) => { sig_recv!("SIGHUP", HangUp); }
144                    None => { sig_restart!("SIGHUP", sig_hup, hangup); }
145                },
146                ret = self.sig_usr1.recv() => match ret {
147                    Some(_) => { sig_recv!("SIGUSR1", UserDefined1); }
148                    None => { sig_restart!("SIGUSR1", sig_usr1, user_defined1); }
149                },
150                ret = self.sig_usr2.recv() => match ret {
151                    Some(_) => { sig_recv!("SIGUSR2", UserDefined2); }
152                    None => { sig_restart!("SIGUSR2", sig_usr2, user_defined2); }
153                },
154            }
155        }
156    }
157}