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}