rocket_community/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/// # extern crate rocket_community as rocket;
89///
90/// # use rocket::figment::{Figment, providers::{Format, Toml}};
91/// use rocket::Config;
92///
93/// // If these are the contents of `Rocket.toml`...
94/// # let toml = Toml::string(r#"
95/// [default.shutdown]
96/// ctrlc = false
97/// signals = ["term", "hup"]
98/// grace = 10
99/// mercy = 5
100/// # force = false
101/// # "#).nested();
102///
103/// // The config parses as follows:
104/// # let config = Config::from(Figment::from(Config::debug_default()).merge(toml));
105/// assert_eq!(config.shutdown.ctrlc, false);
106/// assert_eq!(config.shutdown.grace, 10);
107/// assert_eq!(config.shutdown.mercy, 5);
108/// # assert_eq!(config.shutdown.force, false);
109///
110/// # #[cfg(unix)] {
111/// use rocket::config::Sig;
112///
113/// assert_eq!(config.shutdown.signals.len(), 2);
114/// assert!(config.shutdown.signals.contains(&Sig::Term));
115/// assert!(config.shutdown.signals.contains(&Sig::Hup));
116/// # }
117/// ```
118///
119/// Or, as with all configuration options, programmatically:
120///
121/// ```rust
122/// # extern crate rocket_community as rocket;
123///
124/// # use rocket::figment::{Figment, providers::{Format, Toml}};
125/// use rocket::config::{Config, ShutdownConfig};
126///
127/// #[cfg(unix)]
128/// use rocket::config::Sig;
129///
130/// let config = Config {
131///     shutdown: ShutdownConfig {
132///         ctrlc: false,
133///         #[cfg(unix)]
134///         signals: {
135///             let mut set = std::collections::HashSet::new();
136///             set.insert(Sig::Term);
137///             set.insert(Sig::Hup);
138///             set
139///         },
140///         grace: 10,
141///         mercy: 5,
142///         force: true,
143///         ..Default::default()
144///     },
145///     ..Config::default()
146/// };
147///
148/// assert_eq!(config.shutdown.ctrlc, false);
149/// assert_eq!(config.shutdown.grace, 10);
150/// assert_eq!(config.shutdown.mercy, 5);
151/// assert_eq!(config.shutdown.force, true);
152///
153/// #[cfg(unix)] {
154///     assert_eq!(config.shutdown.signals.len(), 2);
155///     assert!(config.shutdown.signals.contains(&Sig::Term));
156///     assert!(config.shutdown.signals.contains(&Sig::Hup));
157/// }
158/// ```
159#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
160pub struct ShutdownConfig {
161    /// Whether `ctrl-c` (`SIGINT`) initiates a server shutdown.
162    ///
163    /// **default: `true`**
164    #[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
165    pub ctrlc: bool,
166    /// On Unix, a set of signal which trigger a shutdown. On non-Unix, this
167    /// option is unavailable and silently ignored.
168    ///
169    /// **default: { [`Sig::Term`] }**
170    #[cfg(unix)]
171    #[cfg_attr(nightly, doc(cfg(unix)))]
172    pub signals: HashSet<Sig>,
173    /// The grace period: number of seconds to continue to try to finish
174    /// outstanding _server_ I/O for before forcibly terminating it.
175    ///
176    /// **default: `2`**
177    pub grace: u32,
178    /// The mercy period: number of seconds to continue to try to finish
179    /// outstanding _connection_ I/O for before forcibly terminating it.
180    ///
181    /// **default: `3`**
182    pub mercy: u32,
183    /// Whether to force termination of an async runtime that refuses to
184    /// cooperatively shutdown.
185    ///
186    /// Rocket _never_ forcefully terminates a custom runtime, irrespective of
187    /// this value. A server that creates its own async runtime must take care
188    /// to terminate itself if it fails to cooperate.
189    ///
190    /// _**Note:** Rocket only reads this value from sources in the [default
191    /// provider](crate::Config::figment())._
192    ///
193    /// **default: `true`**
194    #[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
195    pub force: bool,
196    /// PRIVATE: This structure may grow (but never change otherwise) in a
197    /// non-breaking release. As such, constructing this structure should
198    /// _always_ be done using a public constructor or update syntax:
199    ///
200    /// ```rust
201    /// # extern crate rocket_community as rocket;
202    ///
203    /// use rocket::config::ShutdownConfig;
204    ///
205    /// let config = ShutdownConfig {
206    ///     grace: 5,
207    ///     mercy: 10,
208    ///     ..Default::default()
209    /// };
210    /// ```
211    #[doc(hidden)]
212    #[serde(skip)]
213    pub __non_exhaustive: (),
214}
215
216impl Default for ShutdownConfig {
217    fn default() -> Self {
218        ShutdownConfig {
219            ctrlc: true,
220            #[cfg(unix)]
221            signals: {
222                let mut set = HashSet::new();
223                set.insert(Sig::Term);
224                set
225            },
226            grace: 2,
227            mercy: 3,
228            force: true,
229            __non_exhaustive: (),
230        }
231    }
232}
233
234impl ShutdownConfig {
235    pub(crate) fn grace(&self) -> Duration {
236        Duration::from_secs(self.grace as u64)
237    }
238
239    pub(crate) fn mercy(&self) -> Duration {
240        Duration::from_secs(self.mercy as u64)
241    }
242
243    #[cfg(unix)]
244    pub(crate) fn signal_stream(&self) -> Option<impl Stream<Item = Sig>> {
245        use tokio::signal::unix::{signal, SignalKind};
246        use tokio_stream::{wrappers::SignalStream, StreamExt, StreamMap};
247
248        if !self.ctrlc && self.signals.is_empty() {
249            return None;
250        }
251
252        let mut signals = self.signals.clone();
253        if self.ctrlc {
254            signals.insert(Sig::Int);
255        }
256
257        let mut map = StreamMap::new();
258        for sig in signals {
259            let sigkind = match sig {
260                Sig::Alrm => SignalKind::alarm(),
261                Sig::Chld => SignalKind::child(),
262                Sig::Hup => SignalKind::hangup(),
263                Sig::Int => SignalKind::interrupt(),
264                Sig::Io => SignalKind::io(),
265                Sig::Pipe => SignalKind::pipe(),
266                Sig::Quit => SignalKind::quit(),
267                Sig::Term => SignalKind::terminate(),
268                Sig::Usr1 => SignalKind::user_defined1(),
269                Sig::Usr2 => SignalKind::user_defined2(),
270            };
271
272            match signal(sigkind) {
273                Ok(signal) => {
274                    map.insert(sig, SignalStream::new(signal));
275                }
276                Err(e) => warn!("Failed to enable `{}` shutdown signal: {}", sig, e),
277            }
278        }
279
280        Some(map.map(|(k, _)| k))
281    }
282
283    #[cfg(not(unix))]
284    pub(crate) fn signal_stream(&self) -> Option<impl Stream<Item = Sig>> {
285        use futures::stream::once;
286        use tokio_stream::StreamExt;
287
288        self.ctrlc
289            .then(|| tokio::signal::ctrl_c())
290            .map(|signal| once(Box::pin(signal)))
291            .map(|stream| {
292                stream.filter_map(|result| {
293                    result
294                        .map(|_| Sig::Int)
295                        .map_err(|e| warn!("Failed to enable `ctrl-c` shutdown signal: {}", e))
296                        .ok()
297                })
298            })
299    }
300}