rocket/shutdown/
config.rs

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