rocket/config/
shutdown.rs

1use std::fmt;
2
3#[cfg(unix)]
4use std::collections::HashSet;
5
6use futures::stream::Stream;
7use serde::{Deserialize, Serialize};
8
9/// A Unix signal for triggering graceful shutdown.
10///
11/// Each variant corresponds to a Unix process signal which can be used to
12/// trigger a graceful shutdown. See [`Shutdown`] for details.
13///
14/// ## (De)serialization
15///
16/// A `Sig` variant serializes and deserializes as a lowercase string equal to
17/// the name of the variant: `"alrm"` for [`Sig::Alrm`], `"chld"` for
18/// [`Sig::Chld`], and so on.
19#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
20#[serde(rename_all = "lowercase")]
21#[cfg_attr(nightly, doc(cfg(unix)))]
22pub enum Sig {
23    /// The `SIGALRM` Unix signal.
24    Alrm,
25    /// The `SIGCHLD` Unix signal.
26    Chld,
27    /// The `SIGHUP` Unix signal.
28    Hup,
29    /// The `SIGINT` Unix signal.
30    Int,
31    /// The `SIGIO` Unix signal.
32    Io,
33    /// The `SIGPIPE` Unix signal.
34    Pipe,
35    /// The `SIGQUIT` Unix signal.
36    Quit,
37    /// The `SIGTERM` Unix signal.
38    Term,
39    /// The `SIGUSR1` Unix signal.
40    Usr1,
41    /// The `SIGUSR2` Unix signal.
42    Usr2
43}
44
45impl fmt::Display for Sig {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        let s = match self {
48            Sig::Alrm => "SIGALRM",
49            Sig::Chld => "SIGCHLD",
50            Sig::Hup => "SIGHUP",
51            Sig::Int => "SIGINT",
52            Sig::Io => "SIGIO",
53            Sig::Pipe => "SIGPIPE",
54            Sig::Quit => "SIGQUIT",
55            Sig::Term => "SIGTERM",
56            Sig::Usr1 => "SIGUSR1",
57            Sig::Usr2 => "SIGUSR2",
58        };
59
60        s.fmt(f)
61    }
62}
63
64/// Graceful shutdown configuration.
65///
66/// # Summary
67///
68/// This structure configures when and how graceful shutdown occurs. The `ctrlc`
69/// and `signals` properties control _when_ and the `grace` and `mercy`
70/// properties control _how_.
71///
72/// When a shutdown is triggered by an externally or internally initiated
73/// [`Shutdown::notify()`], Rocket allows application I/O to make progress for
74/// at most `grace` seconds before initiating connection-level shutdown.
75/// Connection shutdown forcibly terminates _application_ I/O, but connections
76/// are allowed an additional `mercy` seconds to shutdown before being
77/// forcefully terminated. This implies that a _cooperating_ and active remote
78/// client maintaining an open connection can stall shutdown for at most `grace`
79/// seconds, while an _uncooperative_ remote client can stall shutdown for at
80/// most `grace + mercy` seconds.
81///
82/// # Triggers
83///
84/// _All_ graceful shutdowns are initiated via [`Shutdown::notify()`]. Rocket
85/// can be configured to call [`Shutdown::notify()`] automatically on certain
86/// conditions, specified via the `ctrlc` and `signals` properties of this
87/// structure. More specifically, if `ctrlc` is `true` (the default), `ctrl-c`
88/// (`SIGINT`) initiates a server shutdown, and on Unix, `signals` specifies a
89/// list of IPC signals that trigger a shutdown (`["term"]` by default).
90///
91/// [`Shutdown::notify()`]: crate::Shutdown::notify()
92///
93/// # Grace Period
94///
95/// Once a shutdown is triggered, Rocket stops accepting new connections and
96/// waits at most `grace` seconds before initiating connection shutdown.
97/// Applications can `await` the [`Shutdown`](crate::Shutdown) future to detect
98/// a shutdown and cancel any server-initiated I/O, such as from [infinite
99/// responders](crate::response::stream#graceful-shutdown), to avoid abrupt I/O
100/// cancellation.
101///
102/// # Mercy Period
103///
104/// After the grace period has elapsed, Rocket initiates connection shutdown,
105/// allowing connection-level I/O termination such as TLS's `close_notify` to
106/// proceed nominally. Rocket waits at most `mercy` seconds for connections to
107/// shutdown before forcefully terminating all connections.
108///
109/// # Runaway I/O
110///
111/// If tasks are _still_ executing after both periods _and_ a Rocket configured
112/// async runtime is in use, Rocket waits an unspecified amount of time (not to
113/// exceed 1s) and forcefully terminates the asynchronous runtime. This
114/// guarantees that the server process terminates, prohibiting uncooperative,
115/// runaway I/O from preventing shutdown altogether.
116///
117/// A "Rocket configured runtime" is one started by the `#[rocket::main]` and
118/// `#[launch]` attributes. Rocket _never_ forcefully terminates a custom
119/// runtime. A server that creates its own async runtime must take care to
120/// terminate itself if tasks it spawns fail to cooperate.
121///
122/// Under normal circumstances, forced termination should never occur. No use of
123/// "normal" cooperative I/O (that is, via `.await` or `task::spawn()`) should
124/// trigger abrupt termination. Instead, forced cancellation is intended to
125/// prevent _buggy_ code, such as an unintended infinite loop or unknown use of
126/// blocking I/O, from preventing shutdown.
127///
128/// This behavior can be disabled by setting [`Shutdown::force`] to `false`.
129///
130/// # Example
131///
132/// As with all Rocket configuration options, when using the default
133/// [`Config::figment()`](crate::Config::figment()), `Shutdown` can be
134/// configured via a `Rocket.toml` file. As always, defaults are provided
135/// (documented below), and thus configuration only needs to provided to change
136/// defaults.
137///
138/// ```rust
139/// # use rocket::figment::{Figment, providers::{Format, Toml}};
140/// use rocket::Config;
141///
142/// // If these are the contents of `Rocket.toml`...
143/// # let toml = Toml::string(r#"
144/// [default.shutdown]
145/// ctrlc = false
146/// signals = ["term", "hup"]
147/// grace = 10
148/// mercy = 5
149/// # force = false
150/// # "#).nested();
151///
152/// // The config parses as follows:
153/// # let config = Config::from(Figment::from(Config::debug_default()).merge(toml));
154/// assert_eq!(config.shutdown.ctrlc, false);
155/// assert_eq!(config.shutdown.grace, 10);
156/// assert_eq!(config.shutdown.mercy, 5);
157/// # assert_eq!(config.shutdown.force, false);
158///
159/// # #[cfg(unix)] {
160/// use rocket::config::Sig;
161///
162/// assert_eq!(config.shutdown.signals.len(), 2);
163/// assert!(config.shutdown.signals.contains(&Sig::Term));
164/// assert!(config.shutdown.signals.contains(&Sig::Hup));
165/// # }
166/// ```
167///
168/// Or, as with all configuration options, programmatically:
169///
170/// ```rust
171/// # use rocket::figment::{Figment, providers::{Format, Toml}};
172/// use rocket::config::{Config, Shutdown};
173///
174/// #[cfg(unix)]
175/// use rocket::config::Sig;
176///
177/// let config = Config {
178///     shutdown: Shutdown {
179///         ctrlc: false,
180///         #[cfg(unix)]
181///         signals: {
182///             let mut set = std::collections::HashSet::new();
183///             set.insert(Sig::Term);
184///             set.insert(Sig::Hup);
185///             set
186///         },
187///         grace: 10,
188///         mercy: 5,
189///         force: true,
190///         ..Default::default()
191///     },
192///     ..Config::default()
193/// };
194///
195/// assert_eq!(config.shutdown.ctrlc, false);
196/// assert_eq!(config.shutdown.grace, 10);
197/// assert_eq!(config.shutdown.mercy, 5);
198/// assert_eq!(config.shutdown.force, true);
199///
200/// #[cfg(unix)] {
201///     assert_eq!(config.shutdown.signals.len(), 2);
202///     assert!(config.shutdown.signals.contains(&Sig::Term));
203///     assert!(config.shutdown.signals.contains(&Sig::Hup));
204/// }
205/// ```
206#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
207pub struct Shutdown {
208    /// Whether `ctrl-c` (`SIGINT`) initiates a server shutdown.
209    ///
210    /// **default: `true`**
211    #[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
212    pub ctrlc: bool,
213    /// On Unix, a set of signal which trigger a shutdown. On non-Unix, this
214    /// option is unavailable and silently ignored.
215    ///
216    /// **default: { [`Sig::Term`] }**
217    #[cfg(unix)]
218    #[cfg_attr(nightly, doc(cfg(unix)))]
219    pub signals: HashSet<Sig>,
220    /// The grace period: number of seconds to continue to try to finish
221    /// outstanding _server_ I/O for before forcibly terminating it.
222    ///
223    /// **default: `2`**
224    pub grace: u32,
225    /// The mercy period: number of seconds to continue to try to finish
226    /// outstanding _connection_ I/O for before forcibly terminating it.
227    ///
228    /// **default: `3`**
229    pub mercy: u32,
230    /// Whether to force termination of an async runtime that refuses to
231    /// cooperatively shutdown.
232    ///
233    /// Rocket _never_ forcefully terminates a custom runtime, irrespective of
234    /// this value. A server that creates its own async runtime must take care
235    /// to terminate itself if it fails to cooperate.
236    ///
237    /// _**Note:** Rocket only reads this value from sources in the [default
238    /// provider](crate::Config::figment())._
239    ///
240    /// **default: `true`**
241    #[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
242    pub force: bool,
243    /// PRIVATE: This structure may grow (but never change otherwise) in a
244    /// non-breaking release. As such, constructing this structure should
245    /// _always_ be done using a public constructor or update syntax:
246    ///
247    /// ```rust
248    /// use rocket::config::Shutdown;
249    ///
250    /// let config = Shutdown {
251    ///     grace: 5,
252    ///     mercy: 10,
253    ///     ..Default::default()
254    /// };
255    /// ```
256    #[doc(hidden)]
257    #[serde(skip)]
258    pub __non_exhaustive: (),
259}
260
261impl fmt::Display for Shutdown {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        write!(f, "ctrlc = {}, force = {}, ", self.ctrlc, self.force)?;
264
265        #[cfg(unix)] {
266            write!(f, "signals = [")?;
267            for (i, sig) in self.signals.iter().enumerate() {
268                if i != 0 { write!(f, ", ")?; }
269                write!(f, "{}", sig)?;
270            }
271            write!(f, "], ")?;
272        }
273
274        write!(f, "grace = {}s, mercy = {}s", self.grace, self.mercy)?;
275        Ok(())
276    }
277}
278
279impl Default for Shutdown {
280    fn default() -> Self {
281        Shutdown {
282            ctrlc: true,
283            #[cfg(unix)]
284            signals: { let mut set = HashSet::new(); set.insert(Sig::Term); set },
285            grace: 2,
286            mercy: 3,
287            force: true,
288            __non_exhaustive: (),
289        }
290    }
291}
292
293impl Shutdown {
294    #[cfg(unix)]
295    pub(crate) fn signal_stream(&self) -> Option<impl Stream<Item = Sig>> {
296        use tokio_stream::{StreamExt, StreamMap, wrappers::SignalStream};
297        use tokio::signal::unix::{signal, SignalKind};
298
299        if !self.ctrlc && self.signals.is_empty() {
300            return None;
301        }
302
303        let mut signals = self.signals.clone();
304        if self.ctrlc {
305            signals.insert(Sig::Int);
306        }
307
308        let mut map = StreamMap::new();
309        for sig in signals {
310            let sigkind = match sig {
311                Sig::Alrm => SignalKind::alarm(),
312                Sig::Chld => SignalKind::child(),
313                Sig::Hup => SignalKind::hangup(),
314                Sig::Int => SignalKind::interrupt(),
315                Sig::Io => SignalKind::io(),
316                Sig::Pipe => SignalKind::pipe(),
317                Sig::Quit => SignalKind::quit(),
318                Sig::Term => SignalKind::terminate(),
319                Sig::Usr1 => SignalKind::user_defined1(),
320                Sig::Usr2 => SignalKind::user_defined2()
321            };
322
323            match signal(sigkind) {
324                Ok(signal) => { map.insert(sig, SignalStream::new(signal)); },
325                Err(e) => warn!("Failed to enable `{}` shutdown signal: {}", sig, e),
326            }
327        }
328
329        Some(map.map(|(k, _)| k))
330    }
331
332    #[cfg(not(unix))]
333    pub(crate) fn signal_stream(&self) -> Option<impl Stream<Item = Sig>> {
334        use tokio_stream::StreamExt;
335        use futures::stream::once;
336
337        self.ctrlc.then(|| tokio::signal::ctrl_c())
338            .map(|signal| once(Box::pin(signal)))
339            .map(|stream| stream.filter_map(|result| {
340                result.map(|_| Sig::Int)
341                    .map_err(|e| warn!("Failed to enable `ctrl-c` shutdown signal: {}", e))
342                    .ok()
343            }))
344    }
345}