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}