Enable configurable 'ctrl-c' shutdown by default.

This removes the 'ctrl_c_shutdown' feature opting instead for a 'ctrlc'
configuration option. To avoid further merge conflicts with the master
branch, the option is currently read as an extra.

Co-authored-by: Jeb Rosen <jeb@jebrosen.com>
This commit is contained in:
Sergio Benitez 2020-07-08 22:05:54 -07:00
parent 9277ddafdf
commit 824de061c3
4 changed files with 61 additions and 76 deletions

View File

@ -24,7 +24,7 @@ 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<Result<Self::Connection, io::Error>>;
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Self::Connection>>;
}
/// A 'Connection' represents an open connection to a client
@ -125,7 +125,10 @@ impl<L: Listener + Unpin> Accept for Incoming<L> {
type Conn = L::Connection;
type Error = io::Error;
fn poll_accept(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
fn poll_accept(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>
) -> Poll<Option<io::Result<Self::Conn>>> {
self.poll_next(cx).map(Some)
}
}
@ -154,10 +157,8 @@ impl<L: fmt::Debug> fmt::Debug for Incoming<L> {
}
}
pub fn bind_tcp(address: SocketAddr) -> Pin<Box<dyn Future<Output=Result<TcpListener, io::Error>> + Send>> {
Box::pin(async move {
pub async fn bind_tcp(address: SocketAddr) -> io::Result<TcpListener> {
Ok(TcpListener::bind(address).await?)
})
}
impl Listener for TcpListener {
@ -167,7 +168,7 @@ impl Listener for TcpListener {
self.local_addr().ok()
}
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<Result<Self::Connection, io::Error>> {
fn poll_accept(&mut self, cx: &mut Context<'_>) -> Poll<io::Result<Self::Connection>> {
self.poll_accept(cx).map_ok(|(stream, _addr)| stream)
}
}

View File

@ -113,10 +113,11 @@ impl Listener for TlsListener {
}
}
pub fn bind_tls(address: SocketAddr, cert_chain: Vec<Certificate>, key: PrivateKey)
-> Pin<Box<dyn Future<Output=Result<TlsListener, io::Error>> + Send>>
{
Box::pin(async move {
pub async fn bind_tls(
address: SocketAddr,
cert_chain: Vec<Certificate>,
key: PrivateKey
) -> io::Result<TlsListener> {
let listener = TcpListener::bind(address).await?;
let client_auth = rustls::NoClientAuth::new();
@ -126,15 +127,10 @@ pub fn bind_tls(address: SocketAddr, cert_chain: Vec<Certificate>, key: PrivateK
tls_config.ticketer = rustls::Ticketer::new();
tls_config.set_single_cert(cert_chain, key).expect("invalid key");
let acceptor = TlsAcceptor::from(Arc::new(tls_config));
let state = TlsListenerState::Listening;
Ok(TlsListener {
listener,
acceptor,
state: TlsListenerState::Listening,
})
})
Ok(TlsListener { listener, acceptor, state })
}
impl Connection for TlsStream<TcpStream> {

View File

@ -19,16 +19,14 @@ edition = "2018"
all-features = true
[features]
default = ["private-cookies", "ctrl_c_shutdown"]
default = ["private-cookies"]
tls = ["rocket_http/tls"]
private-cookies = ["rocket_http/private-cookies"]
ctrl_c_shutdown = ["tokio/signal"]
[dependencies]
rocket_codegen = { version = "0.5.0-dev", path = "../codegen" }
rocket_http = { version = "0.5.0-dev", path = "../http" }
futures = "0.3.0"
tokio = { version = "0.2.9", features = ["fs", "io-std", "io-util", "rt-threaded", "sync"] }
yansi = "0.5"
log = { version = "0.4", features = ["std"] }
toml = "0.4.7"
@ -42,6 +40,10 @@ atty = "0.2"
async-trait = "0.1"
ref-cast = "1.0"
[dependencies.tokio]
version = "0.2.9"
features = ["fs", "io-std", "io-util", "rt-threaded", "sync", "signal"]
[build-dependencies]
yansi = "0.5"
version_check = "0.9.1"

View File

@ -523,7 +523,9 @@ impl Rocket {
#[derive(Clone)]
struct TokioExecutor;
impl<Fut> hyper::Executor<Fut> for TokioExecutor where Fut: Future + Send + 'static, Fut::Output: Send {
impl<Fut> hyper::Executor<Fut> for TokioExecutor
where Fut: Future + Send + 'static, Fut::Output: Send
{
fn execute(&self, fut: Fut) {
tokio::spawn(fut);
}
@ -945,9 +947,9 @@ impl Rocket {
/// Returns a `Future` that drives the server, listening for and dispatching
/// requests to mounted routes and catchers. The `Future` completes when the
/// server is shut down, via a [`ShutdownHandle`], or encounters a fatal
/// error. If the `ctrl_c_shutdown` feature is enabled, the server will
/// also shut down once `Ctrl-C` is pressed.
/// server is shut down via a [`ShutdownHandle`], encounters a fatal error,
/// or if the the `ctrlc` configuration option is set, when `Ctrl+C` is
/// pressed.
///
/// # Error
///
@ -969,6 +971,7 @@ impl Rocket {
/// ```
pub async fn launch(mut self) -> Result<(), crate::error::Error> {
use std::net::ToSocketAddrs;
use futures::future::Either;
use crate::error::Error::Launch;
self.prelaunch_check().await.map_err(crate::error::Error::Launch)?;
@ -979,71 +982,54 @@ impl Rocket {
Err(e) => return Err(Launch(e.into())),
};
#[cfg(feature = "ctrl_c_shutdown")]
let (
shutdown_handle,
(cancel_ctrl_c_listener_sender, cancel_ctrl_c_listener_receiver)
) = (
self.shutdown_handle.clone(),
oneshot::channel(),
);
// FIXME: Make `ctrlc` a known `Rocket` config option.
// If `ctrl-c` shutdown is enabled, we `select` on `the ctrl-c` signal
// and server. Otherwise, we only wait on the `server`, hence `pending`.
let shutdown_handle = self.shutdown_handle.clone();
let shutdown_signal = match self.config.get_bool("ctrlc") {
Ok(false) => futures::future::pending().boxed(),
_ => tokio::signal::ctrl_c().boxed(),
};
let server = {
macro_rules! listen_on {
($expr:expr) => {{
let listener = match $expr {
Ok(ok) => ok,
Err(err) => return Err(Launch(LaunchError::new(LaunchErrorKind::Bind(err)))),
Err(err) => return Err(Launch(LaunchError::new(LaunchErrorKind::Bind(err))))
};
self.listen_on(listener)
}};
}
#[cfg(feature = "tls")]
{
#[cfg(feature = "tls")] {
if let Some(tls) = self.config.tls.clone() {
listen_on!(crate::http::tls::bind_tls(addr, tls.certs, tls.key).await).boxed()
} else {
listen_on!(crate::http::private::bind_tcp(addr).await).boxed()
}
}
#[cfg(not(feature = "tls"))]
{
listen_on!(crate::http::private::bind_tcp(addr).await)
#[cfg(not(feature = "tls"))] {
listen_on!(crate::http::private::bind_tcp(addr).await).boxed()
}
};
#[cfg(feature = "ctrl_c_shutdown")]
let server = server.inspect(|_| {
let _ = cancel_ctrl_c_listener_sender.send(());
});
#[cfg(feature = "ctrl_c_shutdown")]
{
tokio::spawn(async move {
use futures::future::{select, Either};
let either = select(
tokio::signal::ctrl_c().boxed(),
cancel_ctrl_c_listener_receiver,
).await;
match either {
Either::Left((Ok(()), _)) | Either::Right((_, _)) => shutdown_handle.shutdown(),
Either::Left((Err(err), _)) => {
// Signal handling isn't strictly necessary, so we can skip it
// if necessary. It's a good idea to let the user know we're
// doing so in case they are expecting certain behavior.
let message = "Not listening for shutdown keybinding.";
warn!("{}", Paint::yellow(message));
info_!("Error: {}", err);
}
}
});
}
match futures::future::select(shutdown_signal, server).await {
Either::Left((Ok(()), server)) => {
// Ctrl-was pressed. Signal shutdown, wait for the server.
shutdown_handle.shutdown();
server.await
}
Either::Left((Err(err), server)) => {
// Error setting up ctrl-c signal. Let the user know.
warn!("Failed to enable `ctrl+c` graceful signal shutdown.");
info_!("Error: {}", err);
server.await
}
// Server shut down before Ctrl-C; return the result.
Either::Right((result, _)) => result,
}
}
}
impl std::fmt::Debug for PreLaunchOp {