-
-
Notifications
You must be signed in to change notification settings - Fork 1.6k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
1 parent
a3c44de
commit 168c7d2
Showing
5 changed files
with
281 additions
and
8 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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(); | ||
}, | ||
} | ||
} | ||
} | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,4 +1,5 @@ | ||
mod buf; | ||
pub(crate) mod drain; | ||
mod exec; | ||
pub(crate) mod io; | ||
mod lazy; | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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; | ||
} | ||
} | ||
} |