1use crate::{BootError, BoxFuture, Result};
2use futures_util::StreamExt;
3use std::collections::BTreeSet;
4use std::fmt;
5
6#[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
38pub 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}