feat(server): add Server::with_graceful_shutdown method
This adds a "combinator" method to `Server`, which accepts a user's future to "select" on. All connections received by the `Server` will be tracked, and if the user's future finishes, graceful shutdown will begin. - The listener will be closed immediately. - The currently active connections will all be notified to start a graceful shutdown. For HTTP/1, that means finishing the existing response and using `connection: clone`. For HTTP/2, the graceful `GOAWAY` process is started. - Once all active connections have terminated, the graceful future will return. Closes #1575
This commit is contained in:
115
src/common/drain.rs
Normal file
115
src/common/drain.rs
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use futures::{Async, Future, Poll, Stream};
|
||||||
|
use futures::future::Shared;
|
||||||
|
use futures::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
|
use super::Never;
|
||||||
|
|
||||||
|
pub fn channel() -> (Signal, Watch) {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
let (drained_tx, drained_rx) = mpsc::channel(0);
|
||||||
|
(
|
||||||
|
Signal {
|
||||||
|
drained_rx,
|
||||||
|
tx,
|
||||||
|
},
|
||||||
|
Watch {
|
||||||
|
drained_tx,
|
||||||
|
rx: rx.shared(),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Signal {
|
||||||
|
drained_rx: mpsc::Receiver<Never>,
|
||||||
|
tx: oneshot::Sender<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Draining {
|
||||||
|
drained_rx: mpsc::Receiver<Never>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Watch {
|
||||||
|
drained_tx: mpsc::Sender<Never>,
|
||||||
|
rx: Shared<oneshot::Receiver<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Watching<F, FN> {
|
||||||
|
future: F,
|
||||||
|
state: State<FN>,
|
||||||
|
watch: Watch,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State<F> {
|
||||||
|
Watch(F),
|
||||||
|
Draining,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Signal {
|
||||||
|
pub fn drain(self) -> Draining {
|
||||||
|
let _ = self.tx.send(());
|
||||||
|
Draining {
|
||||||
|
drained_rx: self.drained_rx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for Draining {
|
||||||
|
type Item = ();
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
match try_ready!(self.drained_rx.poll()) {
|
||||||
|
Some(never) => match never {},
|
||||||
|
None => Ok(Async::Ready(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Watch {
|
||||||
|
pub fn watch<F, FN>(self, future: F, on_drain: FN) -> Watching<F, FN>
|
||||||
|
where
|
||||||
|
F: Future,
|
||||||
|
FN: FnOnce(&mut F),
|
||||||
|
{
|
||||||
|
Watching {
|
||||||
|
future,
|
||||||
|
state: State::Watch(on_drain),
|
||||||
|
watch: self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, FN> Future for Watching<F, FN>
|
||||||
|
where
|
||||||
|
F: Future,
|
||||||
|
FN: FnOnce(&mut F),
|
||||||
|
{
|
||||||
|
type Item = F::Item;
|
||||||
|
type Error = F::Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
loop {
|
||||||
|
match mem::replace(&mut self.state, State::Draining) {
|
||||||
|
State::Watch(on_drain) => {
|
||||||
|
match self.watch.rx.poll() {
|
||||||
|
Ok(Async::Ready(_)) | Err(_) => {
|
||||||
|
// Drain has been triggered!
|
||||||
|
on_drain(&mut self.future);
|
||||||
|
},
|
||||||
|
Ok(Async::NotReady) => {
|
||||||
|
self.state = State::Watch(on_drain);
|
||||||
|
return self.future.poll();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
State::Draining => {
|
||||||
|
return self.future.poll();
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
mod buf;
|
mod buf;
|
||||||
|
pub(crate) mod drain;
|
||||||
mod exec;
|
mod exec;
|
||||||
pub(crate) mod io;
|
pub(crate) mod io;
|
||||||
mod lazy;
|
mod lazy;
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ pub struct Connecting<I, F> {
|
|||||||
#[must_use = "futures do nothing unless polled"]
|
#[must_use = "futures do nothing unless polled"]
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(super) struct SpawnAll<I, S> {
|
pub(super) struct SpawnAll<I, S> {
|
||||||
serve: Serve<I, S>,
|
pub(super) serve: Serve<I, S>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A future binding a connection with a Service.
|
/// A future binding a connection with a Service.
|
||||||
@@ -618,7 +618,7 @@ impl<I, S> SpawnAll<I, S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<I, S, B> Future for SpawnAll<I, S>
|
impl<I, S, B> SpawnAll<I, S>
|
||||||
where
|
where
|
||||||
I: Stream,
|
I: Stream,
|
||||||
I::Error: Into<Box<::std::error::Error + Send + Sync>>,
|
I::Error: Into<Box<::std::error::Error + Send + Sync>>,
|
||||||
@@ -630,16 +630,19 @@ where
|
|||||||
<S::Service as Service>::Future: Send + 'static,
|
<S::Service as Service>::Future: Send + 'static,
|
||||||
B: Payload,
|
B: Payload,
|
||||||
{
|
{
|
||||||
type Item = ();
|
pub(super) fn poll_with<F1, F2, R>(&mut self, per_connection: F1) -> Poll<(), ::Error>
|
||||||
type Error = ::Error;
|
where
|
||||||
|
F1: Fn() -> F2,
|
||||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
F2: FnOnce(UpgradeableConnection<I::Item, S::Service>) -> R + Send + 'static,
|
||||||
|
R: Future<Item=(), Error=::Error> + Send + 'static,
|
||||||
|
{
|
||||||
loop {
|
loop {
|
||||||
if let Some(connecting) = try_ready!(self.serve.poll()) {
|
if let Some(connecting) = try_ready!(self.serve.poll()) {
|
||||||
|
let and_then = per_connection();
|
||||||
let fut = connecting
|
let fut = connecting
|
||||||
.map_err(::Error::new_user_new_service)
|
.map_err(::Error::new_user_new_service)
|
||||||
// flatten basically
|
// flatten basically
|
||||||
.and_then(|conn| conn.with_upgrades())
|
.and_then(|conn| and_then(conn.with_upgrades()))
|
||||||
.map_err(|err| debug!("conn error: {}", err));
|
.map_err(|err| debug!("conn error: {}", err));
|
||||||
self.serve.protocol.exec.execute(fut)?;
|
self.serve.protocol.exec.execute(fut)?;
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -51,6 +51,7 @@
|
|||||||
//! ```
|
//! ```
|
||||||
|
|
||||||
pub mod conn;
|
pub mod conn;
|
||||||
|
mod shutdown;
|
||||||
#[cfg(feature = "runtime")] mod tcp;
|
#[cfg(feature = "runtime")] mod tcp;
|
||||||
|
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
@@ -67,6 +68,7 @@ use service::{NewService, Service};
|
|||||||
// Renamed `Http` as `Http_` for now so that people upgrading don't see an
|
// Renamed `Http` as `Http_` for now so that people upgrading don't see an
|
||||||
// error that `hyper::server::Http` is private...
|
// error that `hyper::server::Http` is private...
|
||||||
use self::conn::{Http as Http_, SpawnAll};
|
use self::conn::{Http as Http_, SpawnAll};
|
||||||
|
use self::shutdown::Graceful;
|
||||||
#[cfg(feature = "runtime")] use self::tcp::AddrIncoming;
|
#[cfg(feature = "runtime")] use self::tcp::AddrIncoming;
|
||||||
|
|
||||||
/// A listening HTTP server that accepts connections in both HTTP1 and HTTP2 by default.
|
/// A listening HTTP server that accepts connections in both HTTP1 and HTTP2 by default.
|
||||||
@@ -136,6 +138,65 @@ impl<S> Server<AddrIncoming, S> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<I, S, B> Server<I, S>
|
||||||
|
where
|
||||||
|
I: Stream,
|
||||||
|
I::Error: Into<Box<::std::error::Error + Send + Sync>>,
|
||||||
|
I::Item: AsyncRead + AsyncWrite + Send + 'static,
|
||||||
|
S: NewService<ReqBody=Body, ResBody=B> + Send + 'static,
|
||||||
|
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
|
||||||
|
S::Service: Send,
|
||||||
|
S::Future: Send + 'static,
|
||||||
|
<S::Service as Service>::Future: Send + 'static,
|
||||||
|
B: Payload,
|
||||||
|
{
|
||||||
|
/// Prepares a server to handle graceful shutdown when the provided future
|
||||||
|
/// completes.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # extern crate hyper;
|
||||||
|
/// # extern crate futures;
|
||||||
|
/// # use futures::Future;
|
||||||
|
/// # fn main() {}
|
||||||
|
/// # #[cfg(feature = "runtime")]
|
||||||
|
/// # fn run() {
|
||||||
|
/// # use hyper::{Body, Response, Server};
|
||||||
|
/// # use hyper::service::service_fn_ok;
|
||||||
|
/// # let new_service = || {
|
||||||
|
/// # service_fn_ok(|_req| {
|
||||||
|
/// # Response::new(Body::from("Hello World"))
|
||||||
|
/// # })
|
||||||
|
/// # };
|
||||||
|
///
|
||||||
|
/// // Make a server from the previous examples...
|
||||||
|
/// let server = Server::bind(&([127, 0, 0, 1], 3000).into())
|
||||||
|
/// .serve(new_service);
|
||||||
|
///
|
||||||
|
/// // Prepare some signal for when the server should start
|
||||||
|
/// // shutting down...
|
||||||
|
/// let (tx, rx) = futures::sync::oneshot::channel::<()>();
|
||||||
|
///
|
||||||
|
/// let graceful = server
|
||||||
|
/// .with_graceful_shutdown(rx)
|
||||||
|
/// .map_err(|err| eprintln!("server error: {}", err));
|
||||||
|
///
|
||||||
|
/// // Spawn `server` onto an Executor...
|
||||||
|
/// hyper::rt::spawn(graceful);
|
||||||
|
///
|
||||||
|
/// // And later, trigger the signal by calling `tx.send(())`.
|
||||||
|
/// let _ = tx.send(());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn with_graceful_shutdown<F>(self, signal: F) -> Graceful<I, S, F>
|
||||||
|
where
|
||||||
|
F: Future<Item=()>
|
||||||
|
{
|
||||||
|
Graceful::new(self.spawn_all, signal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<I, S, B> Future for Server<I, S>
|
impl<I, S, B> Future for Server<I, S>
|
||||||
where
|
where
|
||||||
I: Stream,
|
I: Stream,
|
||||||
@@ -152,7 +213,7 @@ where
|
|||||||
type Error = ::Error;
|
type Error = ::Error;
|
||||||
|
|
||||||
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
self.spawn_all.poll()
|
self.spawn_all.poll_with(|| |conn| conn)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
93
src/server/shutdown.rs
Normal file
93
src/server/shutdown.rs
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
use futures::{Async, Future, Stream, Poll};
|
||||||
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
use body::{Body, Payload};
|
||||||
|
use common::drain::{self, Draining, Signal, Watch};
|
||||||
|
use service::{Service, NewService};
|
||||||
|
use super::SpawnAll;
|
||||||
|
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Graceful<I, S, F> {
|
||||||
|
state: State<I, S, F>,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State<I, S, F> {
|
||||||
|
Running {
|
||||||
|
drain: Option<(Signal, Watch)>,
|
||||||
|
spawn_all: SpawnAll<I, S>,
|
||||||
|
signal: F,
|
||||||
|
},
|
||||||
|
Draining(Draining),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, S, F> Graceful<I, S, F> {
|
||||||
|
pub(super) fn new(spawn_all: SpawnAll<I, S>, signal: F) -> Self {
|
||||||
|
let drain = Some(drain::channel());
|
||||||
|
Graceful {
|
||||||
|
state: State::Running {
|
||||||
|
drain,
|
||||||
|
spawn_all,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
impl<I, S, B, F> Future for Graceful<I, S, F>
|
||||||
|
where
|
||||||
|
I: Stream,
|
||||||
|
I::Error: Into<Box<::std::error::Error + Send + Sync>>,
|
||||||
|
I::Item: AsyncRead + AsyncWrite + Send + 'static,
|
||||||
|
S: NewService<ReqBody=Body, ResBody=B> + Send + 'static,
|
||||||
|
S::Error: Into<Box<::std::error::Error + Send + Sync>>,
|
||||||
|
S::Service: Send,
|
||||||
|
S::Future: Send + 'static,
|
||||||
|
<S::Service as Service>::Future: Send + 'static,
|
||||||
|
B: Payload,
|
||||||
|
F: Future<Item=()>,
|
||||||
|
{
|
||||||
|
type Item = ();
|
||||||
|
type Error = ::Error;
|
||||||
|
|
||||||
|
fn poll(&mut self) -> Poll<Self::Item, Self::Error> {
|
||||||
|
loop {
|
||||||
|
let next = match self.state {
|
||||||
|
State::Running {
|
||||||
|
ref mut drain,
|
||||||
|
ref mut spawn_all,
|
||||||
|
ref mut signal,
|
||||||
|
} => match signal.poll() {
|
||||||
|
Ok(Async::Ready(())) | Err(_) => {
|
||||||
|
debug!("signal received, starting graceful shutdown");
|
||||||
|
let sig = drain
|
||||||
|
.take()
|
||||||
|
.expect("drain channel")
|
||||||
|
.0;
|
||||||
|
State::Draining(sig.drain())
|
||||||
|
},
|
||||||
|
Ok(Async::NotReady) => {
|
||||||
|
let watch = &drain
|
||||||
|
.as_ref()
|
||||||
|
.expect("drain channel")
|
||||||
|
.1;
|
||||||
|
return spawn_all.poll_with(|| {
|
||||||
|
let watch = watch.clone();
|
||||||
|
move |conn| {
|
||||||
|
watch.watch(conn, |conn| {
|
||||||
|
// on_drain, start conn graceful shutdown
|
||||||
|
conn.graceful_shutdown()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
},
|
||||||
|
State::Draining(ref mut draining) => {
|
||||||
|
return draining.poll()
|
||||||
|
.map_err(|()| unreachable!("drain mpsc rx never errors"));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
self.state = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user