Skip to main content

a3s_boot/app/
shutdown.rs

1use crate::{BootError, BoxFuture, Result};
2use futures_util::StreamExt;
3use std::collections::BTreeSet;
4use std::fmt;
5
6/// Shutdown signal names understood by Nest-style shutdown hooks.
7#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
8pub enum ShutdownSignal {
9    Sigint,
10    Sigterm,
11    Sigquit,
12    Sighup,
13    Sigusr2,
14}
15
16impl ShutdownSignal {
17    pub fn as_str(self) -> &'static str {
18        match self {
19            Self::Sigint => "SIGINT",
20            Self::Sigterm => "SIGTERM",
21            Self::Sigquit => "SIGQUIT",
22            Self::Sighup => "SIGHUP",
23            Self::Sigusr2 => "SIGUSR2",
24        }
25    }
26
27    pub fn default_signals() -> Vec<Self> {
28        vec![Self::Sigint, Self::Sigterm]
29    }
30}
31
32impl fmt::Display for ShutdownSignal {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        f.write_str(self.as_str())
35    }
36}
37
38/// Wait for one configured operating-system shutdown signal.
39pub async fn wait_for_shutdown_signal<I>(signals: I) -> Result<ShutdownSignal>
40where
41    I: IntoIterator<Item = ShutdownSignal>,
42{
43    let signals = normalize_shutdown_signals(signals);
44    let mut futures = futures_util::stream::FuturesUnordered::new();
45    for signal in signals {
46        futures.push(shutdown_signal_future(signal)?);
47    }
48
49    futures
50        .next()
51        .await
52        .unwrap_or_else(|| Err(BootError::Internal("no shutdown signals configured".into())))
53}
54
55pub(crate) fn normalize_shutdown_signals<I>(signals: I) -> Vec<ShutdownSignal>
56where
57    I: IntoIterator<Item = ShutdownSignal>,
58{
59    let signals = signals.into_iter().collect::<BTreeSet<_>>();
60    if signals.is_empty() {
61        ShutdownSignal::default_signals()
62    } else {
63        signals.into_iter().collect()
64    }
65}
66
67fn shutdown_signal_future(
68    signal: ShutdownSignal,
69) -> Result<BoxFuture<'static, Result<ShutdownSignal>>> {
70    match signal {
71        ShutdownSignal::Sigint => Ok(Box::pin(async move {
72            tokio::signal::ctrl_c().await?;
73            Ok(ShutdownSignal::Sigint)
74        })),
75        #[cfg(unix)]
76        ShutdownSignal::Sigterm => unix_shutdown_signal_future(
77            ShutdownSignal::Sigterm,
78            tokio::signal::unix::SignalKind::terminate(),
79        ),
80        #[cfg(unix)]
81        ShutdownSignal::Sigquit => unix_shutdown_signal_future(
82            ShutdownSignal::Sigquit,
83            tokio::signal::unix::SignalKind::quit(),
84        ),
85        #[cfg(unix)]
86        ShutdownSignal::Sighup => unix_shutdown_signal_future(
87            ShutdownSignal::Sighup,
88            tokio::signal::unix::SignalKind::hangup(),
89        ),
90        #[cfg(unix)]
91        ShutdownSignal::Sigusr2 => unix_shutdown_signal_future(
92            ShutdownSignal::Sigusr2,
93            tokio::signal::unix::SignalKind::user_defined2(),
94        ),
95        #[cfg(not(unix))]
96        signal => Ok(Box::pin(async move {
97            Err(BootError::Internal(format!(
98                "shutdown signal {signal} is only supported on Unix platforms"
99            )))
100        })),
101    }
102}
103
104#[cfg(unix)]
105fn unix_shutdown_signal_future(
106    signal: ShutdownSignal,
107    kind: tokio::signal::unix::SignalKind,
108) -> Result<BoxFuture<'static, Result<ShutdownSignal>>> {
109    let mut stream = tokio::signal::unix::signal(kind)?;
110    Ok(Box::pin(async move {
111        let _ = stream.recv().await;
112        Ok(signal)
113    }))
114}