Skip to content

Commit

Permalink
Implement graceful shutdown.
Browse files Browse the repository at this point in the history
The crux of the implementation is as follows:

  * Configurable ctrl-c, signals that trigger a graceful shutdown.
  * Configurable grace period before forced I/O termination.
  * Programatic triggering via an application-wide method.
  * A future (`Shutdown`) that resolves only when shutdown is requested.

Resolves #180.
  • Loading branch information
SergioBenitez committed Apr 28, 2021
1 parent 63e6845 commit a72e8da
Show file tree
Hide file tree
Showing 12 changed files with 774 additions and 107 deletions.
19 changes: 13 additions & 6 deletions core/http/src/listener.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@ pub trait Listener {
fn local_addr(&self) -> Option<SocketAddr>;

/// Try to accept an incoming Connection if ready
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Self::Connection>>;
fn poll_accept(
self: Pin<&mut Self>,
cx: &mut Context<'_>
) -> Poll<io::Result<Self::Connection>>;
}

/// A 'Connection' represents an open connection to a client
Expand All @@ -40,16 +43,17 @@ pin_project_lite::pin_project! {
/// Accept). This type is internal to Rocket.
#[must_use = "streams do nothing unless polled"]
pub struct Incoming<L> {
listener: L,
sleep_on_errors: Option<Duration>,
#[pin]
pending_error_delay: Option<Sleep>,
#[pin]
listener: L,
}
}

impl<L: Listener> Incoming<L> {
/// Construct an `Incoming` from an existing `Listener`.
pub fn from_listener(listener: L) -> Self {
pub fn new(listener: L) -> Self {
Self {
listener,
sleep_on_errors: Some(Duration::from_millis(250)),
Expand Down Expand Up @@ -96,7 +100,7 @@ impl<L: Listener> Incoming<L> {

me.pending_error_delay.set(None);

match me.listener.poll_accept(cx) {
match me.listener.as_mut().poll_accept(cx) {
Poll::Ready(Ok(stream)) => {
return Poll::Ready(Ok(stream));
},
Expand All @@ -123,7 +127,7 @@ impl<L: Listener> Incoming<L> {
}
}

impl<L: Listener + Unpin> Accept for Incoming<L> {
impl<L: Listener> Accept for Incoming<L> {
type Conn = L::Connection;
type Error = io::Error;

Expand Down Expand Up @@ -171,7 +175,10 @@ impl Listener for TcpListener {
self.local_addr().ok()
}

fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Self::Connection>> {
fn poll_accept(
self: Pin<&mut Self>,
cx: &mut Context<'_>
) -> Poll<io::Result<Self::Connection>> {
(*self).poll_accept(cx).map_ok(|(stream, _addr)| stream)
}
}
Expand Down
5 changes: 4 additions & 1 deletion core/http/src/tls.rs
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,10 @@ impl Listener for TlsListener {
self.listener.local_addr().ok()
}

fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Self::Connection>> {
fn poll_accept(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>
) -> Poll<io::Result<Self::Connection>> {
loop {
match self.state {
TlsListenerState::Listening => {
Expand Down
18 changes: 9 additions & 9 deletions core/lib/src/config/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use figment::value::{Map, Dict};
use serde::{Deserialize, Serialize};
use yansi::Paint;

use crate::config::{TlsConfig, LogLevel};
use crate::config::{TlsConfig, LogLevel, Shutdown};
use crate::request::{self, Request, FromRequest};
use crate::data::Limits;

Expand Down Expand Up @@ -82,17 +82,16 @@ pub struct Config {
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
#[serde(serialize_with = "SecretKey::serialize_zero")]
pub secret_key: SecretKey,
/// The directory to store temporary files in. **(default:
/// [`std::env::temp_dir`]).
/// Directory to store temporary files in. **(default:
/// [`std::env::temp_dir()`])**
pub temp_dir: PathBuf,
/// Max level to log. **(default: _debug_ `normal` / _release_ `critical`)**
pub log_level: LogLevel,
/// Graceful shutdown configuration. **(default: [`Shutdown::default()`])**
pub shutdown: Shutdown,
/// Whether to use colors and emoji when logging. **(default: `true`)**
#[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
pub cli_colors: bool,
/// Whether `ctrl-c` initiates a server shutdown. **(default: `true`)**
#[serde(deserialize_with = "figment::util::bool_from_str_or_int")]
pub ctrlc: bool,
}

impl Default for Config {
Expand Down Expand Up @@ -152,7 +151,7 @@ impl Config {
temp_dir: std::env::temp_dir(),
log_level: LogLevel::Normal,
cli_colors: true,
ctrlc: true,
shutdown: Shutdown::default(),
}
}

Expand Down Expand Up @@ -318,6 +317,7 @@ impl Config {
launch_info_!("temp dir: {}", Paint::default(&self.temp_dir.display()).bold());
launch_info_!("log level: {}", Paint::default(self.log_level).bold());
launch_info_!("cli colors: {}", Paint::default(&self.cli_colors).bold());
launch_info_!("shutdown: {}", Paint::default(&self.shutdown).bold());

// Check for now depreacted config values.
for (key, replacement) in Self::DEPRECATED_KEYS {
Expand Down Expand Up @@ -398,8 +398,8 @@ impl Config {
/// The stringy parameter name for setting/extracting [`Config::log_level`].
pub const LOG_LEVEL: &'static str = "log_level";

/// The stringy parameter name for setting/extracting [`Config::ctrlc`].
pub const CTRLC: &'static str = "ctrlc";
/// The stringy parameter name for setting/extracting [`Config::shutdown`].
pub const SHUTDOWN: &'static str = "shutdown";
}

impl Provider for Config {
Expand Down
12 changes: 9 additions & 3 deletions core/lib/src/config/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@

mod config;
mod tls;
mod shutdown;

#[cfg(feature = "secrets")]
mod secret_key;
Expand All @@ -121,19 +122,24 @@ mod secret_key;

pub use config::Config;
pub use crate::log::LogLevel;
pub use shutdown::Shutdown;
pub use tls::TlsConfig;

#[cfg(feature = "secrets")]
#[cfg_attr(nightly, doc(cfg(feature = "secrets")))]
pub use secret_key::SecretKey;

#[cfg(unix)]
#[cfg_attr(nightly, doc(cfg(unix)))]
pub use shutdown::Sig;

#[cfg(test)]
mod tests {
use std::net::Ipv4Addr;
use figment::{Figment, Profile};
use pretty_assertions::assert_eq;

use crate::config::{Config, TlsConfig};
use crate::config::{Config, TlsConfig, Shutdown};
use crate::log::LogLevel;
use crate::data::{Limits, ToByteUnit};

Expand Down Expand Up @@ -217,7 +223,7 @@ mod tests {

jail.create_file("Rocket.toml", r#"
[global]
ctrlc = 0
shutdown.ctrlc = 0
[global.tls]
certs = "/ssl/cert.pem"
Expand All @@ -231,7 +237,7 @@ mod tests {

let config = Config::from(Config::figment());
assert_eq!(config, Config {
ctrlc: false,
shutdown: Shutdown { ctrlc: false, ..Default::default() },
tls: Some(TlsConfig::from_paths("/ssl/cert.pem", "/ssl/key.pem")),
limits: Limits::default()
.limit("forms", 1.mebibytes())
Expand Down
Loading

0 comments on commit a72e8da

Please sign in to comment.