Add Graceful Shutdown support
If graceful shutdown is initiated, a GOAWAY of the max stream ID - 1 is sent, followed by a PING frame, to measure RTT. When the PING is ACKed, the connection sends a new GOAWAY with the proper last processed stream ID. From there, once all active streams have completely, the connection will finally close.
This commit is contained in:
134
src/proto/go_away.rs
Normal file
134
src/proto/go_away.rs
Normal file
@@ -0,0 +1,134 @@
|
||||
use codec::Codec;
|
||||
use frame::{self, Reason, StreamId};
|
||||
|
||||
use bytes::Buf;
|
||||
use futures::{Async, Poll};
|
||||
use std::io;
|
||||
use tokio_io::AsyncWrite;
|
||||
|
||||
/// Manages our sending of GOAWAY frames.
|
||||
#[derive(Debug)]
|
||||
pub(super) struct GoAway {
|
||||
/// Whether the connection should close now, or wait until idle.
|
||||
close_now: bool,
|
||||
/// Records if we've sent any GOAWAY before.
|
||||
going_away: Option<GoingAway>,
|
||||
|
||||
/// A GOAWAY frame that must be buffered in the Codec immediately.
|
||||
pending: Option<frame::GoAway>,
|
||||
}
|
||||
|
||||
/// Keeps a memory of any GOAWAY frames we've sent before.
|
||||
///
|
||||
/// This looks very similar to a `frame::GoAway`, but is a separate type. Why?
|
||||
/// Mostly for documentation purposes. This type is to record status. If it
|
||||
/// were a `frame::GoAway`, it might appear like we eventually wanted to
|
||||
/// serialize it. We **only** want to be able to look up these fields at a
|
||||
/// later time.
|
||||
///
|
||||
/// (Technically, `frame::GoAway` should gain an opaque_debug_data field as
|
||||
/// well, and we wouldn't want to save that here to accidentally dump in logs,
|
||||
/// or waste struct space.)
|
||||
#[derive(Debug)]
|
||||
struct GoingAway {
|
||||
/// Stores the highest stream ID of a GOAWAY that has been sent.
|
||||
///
|
||||
/// It's illegal to send a subsequent GOAWAY with a higher ID.
|
||||
last_processed_id: StreamId,
|
||||
|
||||
/// Records the error code of any GOAWAY frame sent.
|
||||
reason: Reason,
|
||||
}
|
||||
|
||||
impl GoAway {
|
||||
pub fn new() -> Self {
|
||||
GoAway {
|
||||
close_now: false,
|
||||
going_away: None,
|
||||
pending: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Enqueue a GOAWAY frame to be written.
|
||||
///
|
||||
/// The connection is expected to continue to run until idle.
|
||||
pub fn go_away(&mut self, f: frame::GoAway) {
|
||||
if let Some(ref going_away) = self.going_away {
|
||||
assert!(
|
||||
f.last_stream_id() <= going_away.last_processed_id,
|
||||
"GOAWAY stream IDs shouldn't be higher; \
|
||||
last_processed_id = {:?}, f.last_stream_id() = {:?}",
|
||||
going_away.last_processed_id,
|
||||
f.last_stream_id(),
|
||||
);
|
||||
}
|
||||
|
||||
self.going_away = Some(GoingAway {
|
||||
last_processed_id: f.last_stream_id(),
|
||||
reason: f.reason(),
|
||||
});
|
||||
self.pending = Some(f);
|
||||
}
|
||||
|
||||
pub fn go_away_now(&mut self, f: frame::GoAway) {
|
||||
self.close_now = true;
|
||||
if let Some(ref going_away) = self.going_away {
|
||||
// Prevent sending the same GOAWAY twice.
|
||||
if going_away.last_processed_id == f.last_stream_id()
|
||||
&& going_away.reason == f.reason() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
self.go_away(f);
|
||||
}
|
||||
|
||||
/// Return if a GOAWAY has ever been scheduled.
|
||||
pub fn is_going_away(&self) -> bool {
|
||||
self.going_away.is_some()
|
||||
}
|
||||
|
||||
/// Return the last Reason we've sent.
|
||||
pub fn going_away_reason(&self) -> Option<Reason> {
|
||||
self.going_away
|
||||
.as_ref()
|
||||
.map(|g| g.reason)
|
||||
}
|
||||
|
||||
/// Returns if the connection should close now, or wait until idle.
|
||||
pub fn should_close_now(&self) -> bool {
|
||||
self.pending.is_none() && self.close_now
|
||||
}
|
||||
|
||||
/// Returns if the connection should be closed when idle.
|
||||
pub fn should_close_on_idle(&self) -> bool {
|
||||
!self.close_now && self.going_away
|
||||
.as_ref()
|
||||
.map(|g| g.last_processed_id != StreamId::MAX_CLIENT)
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
/// Try to write a pending GOAWAY frame to the buffer.
|
||||
///
|
||||
/// If a frame is written, the `Reason` of the GOAWAY is returned.
|
||||
pub fn send_pending_go_away<T, B>(&mut self, dst: &mut Codec<T, B>) -> Poll<Option<Reason>, io::Error>
|
||||
where
|
||||
T: AsyncWrite,
|
||||
B: Buf,
|
||||
{
|
||||
if let Some(frame) = self.pending.take() {
|
||||
if !dst.poll_ready()?.is_ready() {
|
||||
self.pending = Some(frame);
|
||||
return Ok(Async::NotReady);
|
||||
}
|
||||
|
||||
let reason = frame.reason();
|
||||
dst.buffer(frame.into())
|
||||
.ok()
|
||||
.expect("invalid GOAWAY frame");
|
||||
|
||||
return Ok(Async::Ready(Some(reason)));
|
||||
}
|
||||
|
||||
Ok(Async::Ready(None))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user