ignore received frames on a stream locally reset for some time (#174)

- Adds config duration for how long to ignore frames on a reset stream
- Adds config for how many reset streams can be held at a time
This commit is contained in:
Sean McArthur
2017-12-18 11:09:38 -08:00
committed by GitHub
parent edaeaa8941
commit 1ea9a8fc7e
19 changed files with 684 additions and 125 deletions

View File

@@ -12,6 +12,7 @@ use tokio_io::io::WriteAll;
use std::fmt; use std::fmt;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::time::Duration;
/// In progress H2 connection binding /// In progress H2 connection binding
#[must_use = "futures do nothing unless polled"] #[must_use = "futures do nothing unless polled"]
@@ -45,6 +46,12 @@ pub struct ResponseFuture {
/// Build a Client. /// Build a Client.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct Builder { pub struct Builder {
/// Time to keep locally reset streams around before reaping.
reset_stream_duration: Duration,
/// Maximum number of locally reset streams to keep at a time.
reset_stream_max: usize,
/// Initial `Settings` frame to send as part of the handshake. /// Initial `Settings` frame to send as part of the handshake.
settings: Settings, settings: Settings,
@@ -208,6 +215,26 @@ impl Builder {
self self
} }
/// Set the maximum number of concurrent locally reset streams.
///
/// Locally reset streams are to "ignore frames from the peer for some
/// time". While waiting for that time, locally reset streams "waste"
/// space in order to be able to ignore those frames. This setting
/// can limit how many extra streams are left waiting for "some time".
pub fn max_concurrent_reset_streams(&mut self, max: usize) -> &mut Self {
self.reset_stream_max = max;
self
}
/// Set the maximum number of concurrent locally reset streams.
///
/// Locally reset streams are to "ignore frames from the peer for some
/// time", but that time is unspecified. Set that time with this setting.
pub fn reset_stream_duration(&mut self, dur: Duration) -> &mut Self {
self.reset_stream_duration = dur;
self
}
/// Enable or disable the server to send push promises. /// Enable or disable the server to send push promises.
pub fn enable_push(&mut self, enabled: bool) -> &mut Self { pub fn enable_push(&mut self, enabled: bool) -> &mut Self {
self.settings.set_enable_push(enabled); self.settings.set_enable_push(enabled);
@@ -245,6 +272,8 @@ impl Builder {
impl Default for Builder { impl Default for Builder {
fn default() -> Builder { fn default() -> Builder {
Builder { Builder {
reset_stream_duration: Duration::from_secs(proto::DEFAULT_RESET_STREAM_SECS),
reset_stream_max: proto::DEFAULT_RESET_STREAM_MAX,
settings: Default::default(), settings: Default::default(),
stream_id: 1.into(), stream_id: 1.into(),
} }
@@ -324,8 +353,12 @@ where
.buffer(self.builder.settings.clone().into()) .buffer(self.builder.settings.clone().into())
.expect("invalid SETTINGS frame"); .expect("invalid SETTINGS frame");
let connection = let connection = proto::Connection::new(codec, proto::Config {
proto::Connection::new(codec, &self.builder.settings, self.builder.stream_id); next_stream_id: self.builder.stream_id,
reset_stream_duration: self.builder.reset_stream_duration,
reset_stream_max: self.builder.reset_stream_max,
settings: self.builder.settings.clone(),
});
let client = Client { let client = Client {
inner: connection.streams().clone(), inner: connection.streams().clone(),
pending: None, pending: None,

View File

@@ -5,14 +5,14 @@ use bytes::{BigEndian, BufMut};
#[derive(Debug, Clone, Copy, Eq, PartialEq)] #[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub struct GoAway { pub struct GoAway {
last_stream_id: StreamId, last_stream_id: StreamId,
error_code: u32, error_code: Reason,
} }
impl GoAway { impl GoAway {
pub fn new(last_stream_id: StreamId, reason: Reason) -> Self { pub fn new(last_stream_id: StreamId, reason: Reason) -> Self {
GoAway { GoAway {
last_stream_id, last_stream_id,
error_code: reason.into(), error_code: reason,
} }
} }
@@ -21,7 +21,7 @@ impl GoAway {
} }
pub fn reason(&self) -> Reason { pub fn reason(&self) -> Reason {
self.error_code.into() self.error_code
} }
pub fn load(payload: &[u8]) -> Result<GoAway, Error> { pub fn load(payload: &[u8]) -> Result<GoAway, Error> {
@@ -34,16 +34,16 @@ impl GoAway {
Ok(GoAway { Ok(GoAway {
last_stream_id: last_stream_id, last_stream_id: last_stream_id,
error_code: error_code, error_code: error_code.into(),
}) })
} }
pub fn encode<B: BufMut>(&self, dst: &mut B) { pub fn encode<B: BufMut>(&self, dst: &mut B) {
trace!("encoding GO_AWAY; code={}", self.error_code); trace!("encoding GO_AWAY; code={:?}", self.error_code);
let head = Head::new(Kind::GoAway, 0, StreamId::zero()); let head = Head::new(Kind::GoAway, 0, StreamId::zero());
head.encode(8, dst); head.encode(8, dst);
dst.put_u32::<BigEndian>(self.last_stream_id.into()); dst.put_u32::<BigEndian>(self.last_stream_id.into());
dst.put_u32::<BigEndian>(self.error_code); dst.put_u32::<BigEndian>(self.error_code.into());
} }
} }

View File

@@ -5,14 +5,14 @@ use bytes::{BigEndian, BufMut};
#[derive(Debug, Eq, PartialEq)] #[derive(Debug, Eq, PartialEq)]
pub struct Reset { pub struct Reset {
stream_id: StreamId, stream_id: StreamId,
error_code: u32, error_code: Reason,
} }
impl Reset { impl Reset {
pub fn new(stream_id: StreamId, error: Reason) -> Reset { pub fn new(stream_id: StreamId, error: Reason) -> Reset {
Reset { Reset {
stream_id, stream_id,
error_code: error.into(), error_code: error,
} }
} }
@@ -21,7 +21,7 @@ impl Reset {
} }
pub fn reason(&self) -> Reason { pub fn reason(&self) -> Reason {
self.error_code.into() self.error_code
} }
pub fn load(head: Head, payload: &[u8]) -> Result<Reset, Error> { pub fn load(head: Head, payload: &[u8]) -> Result<Reset, Error> {
@@ -33,19 +33,19 @@ impl Reset {
Ok(Reset { Ok(Reset {
stream_id: head.stream_id(), stream_id: head.stream_id(),
error_code: error_code, error_code: error_code.into(),
}) })
} }
pub fn encode<B: BufMut>(&self, dst: &mut B) { pub fn encode<B: BufMut>(&self, dst: &mut B) {
trace!( trace!(
"encoding RESET; id={:?} code={}", "encoding RESET; id={:?} code={:?}",
self.stream_id, self.stream_id,
self.error_code self.error_code
); );
let head = Head::new(Kind::Reset, 0, self.stream_id); let head = Head::new(Kind::Reset, 0, self.stream_id);
head.encode(4, dst); head.encode(4, dst);
dst.put_u32::<BigEndian>(self.error_code); dst.put_u32::<BigEndian>(self.error_code.into());
} }
} }

View File

@@ -1,6 +1,6 @@
use {client, frame, proto, server}; use {client, frame, proto, server};
use codec::RecvError; use codec::RecvError;
use frame::Reason; use frame::{Reason, StreamId};
use frame::DEFAULT_INITIAL_WINDOW_SIZE; use frame::DEFAULT_INITIAL_WINDOW_SIZE;
use proto::*; use proto::*;
@@ -10,6 +10,7 @@ use futures::Stream;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use std::marker::PhantomData; use std::marker::PhantomData;
use std::time::Duration;
/// An H2 connection /// An H2 connection
#[derive(Debug)] #[derive(Debug)]
@@ -42,6 +43,14 @@ where
_phantom: PhantomData<P>, _phantom: PhantomData<P>,
} }
#[derive(Debug, Clone)]
pub(crate) struct Config {
pub next_stream_id: StreamId,
pub reset_stream_duration: Duration,
pub reset_stream_max: usize,
pub settings: frame::Settings,
}
#[derive(Debug)] #[derive(Debug)]
enum State { enum State {
/// Currently open in a sane state /// Currently open in a sane state
@@ -65,18 +74,19 @@ where
{ {
pub fn new( pub fn new(
codec: Codec<T, Prioritized<B::Buf>>, codec: Codec<T, Prioritized<B::Buf>>,
settings: &frame::Settings, config: Config,
next_stream_id: frame::StreamId,
) -> Connection<T, P, B> { ) -> Connection<T, P, B> {
let streams = Streams::new(streams::Config { let streams = Streams::new(streams::Config {
local_init_window_sz: settings local_init_window_sz: config.settings
.initial_window_size() .initial_window_size()
.unwrap_or(DEFAULT_INITIAL_WINDOW_SIZE), .unwrap_or(DEFAULT_INITIAL_WINDOW_SIZE),
local_max_initiated: None, local_max_initiated: None,
local_next_stream_id: next_stream_id, local_next_stream_id: config.next_stream_id,
local_push_enabled: settings.is_push_enabled(), local_push_enabled: config.settings.is_push_enabled(),
local_reset_duration: config.reset_stream_duration,
local_reset_max: config.reset_stream_max,
remote_init_window_sz: DEFAULT_INITIAL_WINDOW_SIZE, remote_init_window_sz: DEFAULT_INITIAL_WINDOW_SIZE,
remote_max_initiated: settings remote_max_initiated: config.settings
.max_concurrent_streams() .max_concurrent_streams()
.map(|max| max as usize), .map(|max| max as usize),
}); });
@@ -230,6 +240,11 @@ where
fn poll2(&mut self) -> Poll<(), RecvError> { fn poll2(&mut self) -> Poll<(), RecvError> {
use frame::Frame::*; use frame::Frame::*;
// This happens outside of the loop to prevent needing to do a clock
// check and then comparison of the queue possibly multiple times a
// second (and thus, the clock wouldn't have changed enough to matter).
self.clear_expired_reset_streams();
loop { loop {
// First, ensure that the `Connection` is able to receive a frame // First, ensure that the `Connection` is able to receive a frame
try_ready!(self.poll_ready()); try_ready!(self.poll_ready());
@@ -284,6 +299,10 @@ where
} }
} }
} }
fn clear_expired_reset_streams(&mut self) {
self.streams.clear_expired_reset_streams();
}
} }
impl<T, B> Connection<T, client::Peer, B> impl<T, B> Connection<T, client::Peer, B>

View File

@@ -5,7 +5,7 @@ mod ping_pong;
mod settings; mod settings;
mod streams; mod streams;
pub(crate) use self::connection::Connection; pub(crate) use self::connection::{Config, Connection};
pub(crate) use self::error::Error; pub(crate) use self::error::Error;
pub(crate) use self::peer::{Peer, Dyn as DynPeer}; pub(crate) use self::peer::{Peer, Dyn as DynPeer};
pub(crate) use self::streams::{Key as StreamKey, StreamRef, OpaqueStreamRef, Streams}; pub(crate) use self::streams::{Key as StreamKey, StreamRef, OpaqueStreamRef, Streams};
@@ -31,3 +31,5 @@ pub type WindowSize = u32;
// Constants // Constants
pub const MAX_WINDOW_SIZE: WindowSize = (1 << 31) - 1; pub const MAX_WINDOW_SIZE: WindowSize = (1 << 31) - 1;
pub const DEFAULT_RESET_STREAM_MAX: usize = 10;
pub const DEFAULT_RESET_STREAM_SECS: u64 = 30;

View File

@@ -19,6 +19,12 @@ pub(super) struct Counts {
/// Current number of locally initiated streams /// Current number of locally initiated streams
num_recv_streams: usize, num_recv_streams: usize,
/// Maximum number of pending locally reset streams
max_reset_streams: usize,
/// Current number of pending locally reset streams
num_reset_streams: usize,
} }
impl Counts { impl Counts {
@@ -30,6 +36,8 @@ impl Counts {
num_send_streams: 0, num_send_streams: 0,
max_recv_streams: config.remote_max_initiated.unwrap_or(usize::MAX), max_recv_streams: config.remote_max_initiated.unwrap_or(usize::MAX),
num_recv_streams: 0, num_recv_streams: 0,
max_reset_streams: config.local_reset_max,
num_reset_streams: 0,
} }
} }
@@ -72,6 +80,22 @@ impl Counts {
self.num_send_streams += 1; self.num_send_streams += 1;
} }
/// Returns true if the number of pending reset streams can be incremented.
pub fn can_inc_num_reset_streams(&self) -> bool {
self.max_reset_streams > self.num_reset_streams
}
/// Increments the number of pending reset streams.
///
/// # Panics
///
/// Panics on failure as this should have been validated before hand.
pub fn inc_num_reset_streams(&mut self) {
assert!(self.can_inc_num_reset_streams());
self.num_reset_streams += 1;
}
pub fn apply_remote_settings(&mut self, settings: &frame::Settings) { pub fn apply_remote_settings(&mut self, settings: &frame::Settings) {
if let Some(val) = settings.max_concurrent_streams() { if let Some(val) = settings.max_concurrent_streams() {
self.max_send_streams = val as usize; self.max_send_streams = val as usize;
@@ -87,19 +111,26 @@ impl Counts {
F: FnOnce(&mut Self, &mut store::Ptr) -> U, F: FnOnce(&mut Self, &mut store::Ptr) -> U,
{ {
let is_counted = stream.is_counted(); let is_counted = stream.is_counted();
let is_pending_reset = stream.is_pending_reset_expiration();
// Run the action // Run the action
let ret = f(self, &mut stream); let ret = f(self, &mut stream);
self.transition_after(stream, is_counted); self.transition_after(stream, is_counted, is_pending_reset);
ret ret
} }
// TODO: move this to macro? // TODO: move this to macro?
pub fn transition_after(&mut self, mut stream: store::Ptr, is_counted: bool) { pub fn transition_after(&mut self, mut stream: store::Ptr, is_counted: bool, is_reset_counted: bool) {
if stream.is_closed() { if stream.is_closed() {
stream.unlink(); if !stream.is_pending_reset_expiration() {
stream.unlink();
if is_reset_counted {
self.dec_num_reset_streams();
}
}
if is_counted { if is_counted {
// Decrement the number of active streams. // Decrement the number of active streams.
@@ -115,9 +146,16 @@ impl Counts {
fn dec_num_streams(&mut self, id: StreamId) { fn dec_num_streams(&mut self, id: StreamId) {
if self.peer.is_local_init(id) { if self.peer.is_local_init(id) {
assert!(self.num_send_streams > 0);
self.num_send_streams -= 1; self.num_send_streams -= 1;
} else { } else {
assert!(self.num_recv_streams > 0);
self.num_recv_streams -= 1; self.num_recv_streams -= 1;
} }
} }
fn dec_num_reset_streams(&mut self) {
assert!(self.num_reset_streams > 0);
self.num_reset_streams -= 1;
}
} }

View File

@@ -20,12 +20,13 @@ use self::prioritize::Prioritize;
use self::recv::Recv; use self::recv::Recv;
use self::send::Send; use self::send::Send;
use self::state::State; use self::state::State;
use self::store::{Entry, Store}; use self::store::Store;
use self::stream::Stream; use self::stream::Stream;
use frame::{StreamId, StreamIdOverflow}; use frame::{StreamId, StreamIdOverflow};
use proto::*; use proto::*;
use std::time::Duration;
use bytes::Bytes; use bytes::Bytes;
use http::{Request, Response}; use http::{Request, Response};
@@ -43,6 +44,12 @@ pub struct Config {
/// If the local peer is willing to receive push promises /// If the local peer is willing to receive push promises
pub local_push_enabled: bool, pub local_push_enabled: bool,
/// How long a locally reset stream should ignore frames
pub local_reset_duration: Duration,
/// Maximum number of locally reset streams to keep at a time
pub local_reset_max: usize,
/// Initial window size of remote initiated streams /// Initial window size of remote initiated streams
pub remote_init_window_sz: WindowSize, pub remote_init_window_sz: WindowSize,

View File

@@ -529,7 +529,13 @@ impl Prioritize {
Some(mut stream) => { Some(mut stream) => {
trace!("pop_frame; stream={:?}", stream.id); trace!("pop_frame; stream={:?}", stream.id);
// It's possible that this stream, besides having data to send,
// is also queued to send a reset, and thus is already in the queue
// to wait for "some time" after a reset.
//
// To be safe, we just always ask the stream.
let is_counted = stream.is_counted(); let is_counted = stream.is_counted();
let is_pending_reset = stream.is_pending_reset_expiration();
let frame = match stream.pending_send.pop_front(buffer) { let frame = match stream.pending_send.pop_front(buffer) {
Some(Frame::Data(mut frame)) => { Some(Frame::Data(mut frame)) => {
@@ -651,7 +657,7 @@ impl Prioritize {
self.pending_send.push(&mut stream); self.pending_send.push(&mut stream);
} }
counts.transition_after(stream, is_counted); counts.transition_after(stream, is_counted, is_pending_reset);
return Some(frame); return Some(frame);
}, },

View File

@@ -2,11 +2,11 @@ use super::*;
use {frame, proto}; use {frame, proto};
use codec::{RecvError, UserError}; use codec::{RecvError, UserError};
use frame::{Reason, DEFAULT_INITIAL_WINDOW_SIZE}; use frame::{Reason, DEFAULT_INITIAL_WINDOW_SIZE};
use proto::*;
use http::HeaderMap; use http::HeaderMap;
use std::io; use std::io;
use std::time::{Duration, Instant};
#[derive(Debug)] #[derive(Debug)]
pub(super) struct Recv { pub(super) struct Recv {
@@ -31,6 +31,12 @@ pub(super) struct Recv {
/// New streams to be accepted /// New streams to be accepted
pending_accept: store::Queue<stream::NextAccept>, pending_accept: store::Queue<stream::NextAccept>,
/// Locally reset streams that should be reaped when they expire
pending_reset_expired: store::Queue<stream::NextResetExpire>,
/// How long locally reset streams should ignore received frames
reset_duration: Duration,
/// Holds frames that are waiting to be read /// Holds frames that are waiting to be read
buffer: Buffer<Event>, buffer: Buffer<Event>,
@@ -74,6 +80,8 @@ impl Recv {
pending_window_updates: store::Queue::new(), pending_window_updates: store::Queue::new(),
last_processed_id: StreamId::zero(), last_processed_id: StreamId::zero(),
pending_accept: store::Queue::new(), pending_accept: store::Queue::new(),
pending_reset_expired: store::Queue::new(),
reset_duration: config.local_reset_duration,
buffer: Buffer::new(), buffer: Buffer::new(),
refused: None, refused: None,
is_push_enabled: config.local_push_enabled, is_push_enabled: config.local_push_enabled,
@@ -237,7 +245,28 @@ impl Recv {
Ok(()) Ok(())
} }
/// Releases capacity back to the connection /// Releases capacity of the connection
fn release_connection_capacity(
&mut self,
capacity: WindowSize,
task: &mut Option<Task>,
) {
trace!("release_connection_capacity; size={}", capacity);
// Decrement in-flight data
self.in_flight_data -= capacity;
// Assign capacity to connection
self.flow.assign_capacity(capacity);
if self.flow.unclaimed_capacity().is_some() {
if let Some(task) = task.take() {
task.notify();
}
}
}
/// Releases capacity back to the connection & stream
pub fn release_capacity( pub fn release_capacity(
&mut self, &mut self,
capacity: WindowSize, capacity: WindowSize,
@@ -250,19 +279,14 @@ impl Recv {
return Err(UserError::ReleaseCapacityTooBig); return Err(UserError::ReleaseCapacityTooBig);
} }
self.release_connection_capacity(capacity, task);
// Decrement in-flight data // Decrement in-flight data
stream.in_flight_recv_data -= capacity; stream.in_flight_recv_data -= capacity;
self.in_flight_data -= capacity;
// Assign capacity to connection & stream // Assign capacity to stream
self.flow.assign_capacity(capacity);
stream.recv_flow.assign_capacity(capacity); stream.recv_flow.assign_capacity(capacity);
if self.flow.unclaimed_capacity().is_some() {
if let Some(task) = task.take() {
task.notify();
}
}
if stream.recv_flow.unclaimed_capacity().is_some() { if stream.recv_flow.unclaimed_capacity().is_some() {
// Queue the stream for sending the WINDOW_UPDATE frame. // Queue the stream for sending the WINDOW_UPDATE frame.
@@ -353,8 +377,12 @@ impl Recv {
let sz = sz as WindowSize; let sz = sz as WindowSize;
if !stream.state.is_recv_streaming() { let is_ignoring_frame = stream.state.is_local_reset();
trace!("stream is not in receiving state; state={:?}", stream.state);
if !is_ignoring_frame && !stream.state.is_recv_streaming() {
// TODO: There are cases where this can be a stream error of
// STREAM_CLOSED instead...
// Receiving a DATA frame when not expecting one is a protocol // Receiving a DATA frame when not expecting one is a protocol
// error. // error.
return Err(RecvError::Connection(Reason::PROTOCOL_ERROR)); return Err(RecvError::Connection(Reason::PROTOCOL_ERROR));
@@ -369,19 +397,46 @@ impl Recv {
// Ensure that there is enough capacity on the connection before acting // Ensure that there is enough capacity on the connection before acting
// on the stream. // on the stream.
if self.flow.window_size() < sz || stream.recv_flow.window_size() < sz { self.consume_connection_window(sz)?;
return Err(RecvError::Connection(Reason::FLOW_CONTROL_ERROR));
if is_ignoring_frame {
trace!(
"recv_data frame ignored on locally reset {:?} for some time",
stream.id,
);
// we just checked for enough connection window capacity, and
// consumed it. Since we are ignoring this frame "for some time",
// we aren't returning the frame to the user. That means they
// have no way to release the capacity back to the connection. So
// we have to release it automatically.
//
// This call doesn't send a WINDOW_UPDATE immediately, just marks
// the capacity as available to be reclaimed. When the available
// capacity meets a threshold, a WINDOW_UPDATE is then sent.
self.release_connection_capacity(sz, &mut None);
return Ok(());
} }
// Update connection level flow control if stream.recv_flow.window_size() < sz {
self.flow.send_data(sz); // http://httpwg.org/specs/rfc7540.html#WINDOW_UPDATE
// > A receiver MAY respond with a stream error (Section 5.4.2) or
// > connection error (Section 5.4.1) of type FLOW_CONTROL_ERROR if
// > it is unable to accept a frame.
//
// So, for violating the **stream** window, we can send either a
// stream or connection error. We've opted to send a stream
// error.
return Err(RecvError::Stream {
id: stream.id,
reason: Reason::FLOW_CONTROL_ERROR,
});
}
// Update stream level flow control // Update stream level flow control
stream.recv_flow.send_data(sz); stream.recv_flow.send_data(sz);
// Track the data as in-flight // Track the data as in-flight
stream.in_flight_recv_data += sz; stream.in_flight_recv_data += sz;
self.in_flight_data += sz;
if stream.dec_content_length(frame.payload().len()).is_err() { if stream.dec_content_length(frame.payload().len()).is_err() {
trace!("content-length overflow"); trace!("content-length overflow");
@@ -415,6 +470,19 @@ impl Recv {
Ok(()) Ok(())
} }
pub fn consume_connection_window(&mut self, sz: WindowSize) -> Result<(), RecvError> {
if self.flow.window_size() < sz {
return Err(RecvError::Connection(Reason::FLOW_CONTROL_ERROR));
}
// Update connection level flow control
self.flow.send_data(sz);
// Track the data as in-flight
self.in_flight_data += sz;
Ok(())
}
pub fn recv_push_promise( pub fn recv_push_promise(
&mut self, &mut self,
frame: frame::PushPromise, frame: frame::PushPromise,
@@ -480,15 +548,14 @@ impl Recv {
Ok(()) Ok(())
} }
/// Handle remote sending an explicit RST_STREAM.
pub fn recv_reset( pub fn recv_reset(
&mut self, &mut self,
frame: frame::Reset, frame: frame::Reset,
stream: &mut Stream, stream: &mut Stream,
) -> Result<(), RecvError> { ) -> Result<(), RecvError> {
let err = proto::Error::Proto(frame.reason());
// Notify the stream // Notify the stream
stream.state.recv_err(&err); stream.state.recv_reset(frame.reason());
stream.notify_recv(); stream.notify_recv();
Ok(()) Ok(())
} }
@@ -536,6 +603,38 @@ impl Recv {
Ok(()) Ok(())
} }
/// Add a locally reset stream to queue to be eventually reaped.
pub fn enqueue_reset_expiration(
&mut self,
stream: &mut store::Ptr,
counts: &mut Counts,
) {
assert!(stream.state.is_local_reset());
if stream.is_pending_reset_expiration() {
return;
}
if !counts.can_inc_num_reset_streams() {
// try to evict 1 stream if possible
// if max allow is 0, this won't be able to evict,
// and then we'll just bail after
if let Some(evicted) = self.pending_reset_expired.pop(stream.store_mut()) {
// It's possible that this stream is still sitting in a send queue,
// such as if some data is to be sent and then a CANCEL. In this case,
// it could still be "counted", so we just make sure to always ask the
// stream instead of assuming.
let is_counted = evicted.is_counted();
counts.transition_after(evicted, is_counted, true);
}
}
if counts.can_inc_num_reset_streams() {
counts.inc_num_reset_streams();
self.pending_reset_expired.push(stream);
}
}
/// Send any pending refusals. /// Send any pending refusals.
pub fn send_pending_refusal<T, B>( pub fn send_pending_refusal<T, B>(
&mut self, &mut self,
@@ -562,6 +661,18 @@ impl Recv {
Ok(Async::Ready(())) Ok(Async::Ready(()))
} }
pub fn clear_expired_reset_streams(&mut self, store: &mut Store, counts: &mut Counts) {
let now = Instant::now();
let reset_duration = self.reset_duration;
while let Some(stream) = self.pending_reset_expired.pop_if(store, |stream| {
let reset_at = stream.reset_at.expect("reset_at must be set if in queue");
now - reset_at > reset_duration
}) {
let is_counted = stream.is_counted();
counts.transition_after(stream, is_counted, true);
}
}
pub fn poll_complete<T, B>( pub fn poll_complete<T, B>(
&mut self, &mut self,
store: &mut Store, store: &mut Store,

View File

@@ -3,7 +3,6 @@ use super::*;
use codec::{RecvError, UserError}; use codec::{RecvError, UserError};
use codec::UserError::*; use codec::UserError::*;
use frame::{self, Reason}; use frame::{self, Reason};
use proto::*;
use bytes::Buf; use bytes::Buf;

View File

@@ -60,8 +60,7 @@ enum Inner {
Open { local: Peer, remote: Peer }, Open { local: Peer, remote: Peer },
HalfClosedLocal(Peer), // TODO: explicitly name this value HalfClosedLocal(Peer), // TODO: explicitly name this value
HalfClosedRemote(Peer), HalfClosedRemote(Peer),
// When reset, a reason is provided Closed(Cause),
Closed(Option<Cause>),
} }
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
@@ -72,7 +71,9 @@ enum Peer {
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
enum Cause { enum Cause {
EndStream,
Proto(Reason), Proto(Reason),
LocallyReset(Reason),
Io, Io,
/// The user droped all handles to the stream without explicitly canceling. /// The user droped all handles to the stream without explicitly canceling.
@@ -84,7 +85,7 @@ enum Cause {
impl State { impl State {
/// Opens the send-half of a stream if it is not already open. /// Opens the send-half of a stream if it is not already open.
pub fn send_open(&mut self, eos: bool) -> Result<(), UserError> { pub fn send_open(&mut self, eos: bool) -> Result<(), UserError> {
let local = Peer::Streaming; let local = Streaming;
self.inner = match self.inner { self.inner = match self.inner {
Idle => if eos { Idle => if eos {
@@ -107,7 +108,7 @@ impl State {
} }
}, },
HalfClosedRemote(AwaitingHeaders) => if eos { HalfClosedRemote(AwaitingHeaders) => if eos {
Closed(None) Closed(Cause::EndStream)
} else { } else {
HalfClosedRemote(local) HalfClosedRemote(local)
}, },
@@ -124,7 +125,7 @@ impl State {
/// ///
/// Returns true if this transitions the state to Open. /// Returns true if this transitions the state to Open.
pub fn recv_open(&mut self, eos: bool) -> Result<bool, RecvError> { pub fn recv_open(&mut self, eos: bool) -> Result<bool, RecvError> {
let remote = Peer::Streaming; let remote = Streaming;
let mut initial = false; let mut initial = false;
self.inner = match self.inner { self.inner = match self.inner {
@@ -144,7 +145,7 @@ impl State {
initial = true; initial = true;
if eos { if eos {
Closed(None) Closed(Cause::EndStream)
} else { } else {
Open { Open {
local: AwaitingHeaders, local: AwaitingHeaders,
@@ -164,7 +165,7 @@ impl State {
} }
}, },
HalfClosedLocal(AwaitingHeaders) => if eos { HalfClosedLocal(AwaitingHeaders) => if eos {
Closed(None) Closed(Cause::EndStream)
} else { } else {
HalfClosedLocal(remote) HalfClosedLocal(remote)
}, },
@@ -201,13 +202,25 @@ impl State {
}, },
HalfClosedLocal(..) => { HalfClosedLocal(..) => {
trace!("recv_close: HalfClosedLocal => Closed"); trace!("recv_close: HalfClosedLocal => Closed");
self.inner = Closed(None); self.inner = Closed(Cause::EndStream);
Ok(()) Ok(())
}, },
_ => Err(RecvError::Connection(Reason::PROTOCOL_ERROR)), _ => Err(RecvError::Connection(Reason::PROTOCOL_ERROR)),
} }
} }
/// The remote explicitly sent a RST_STREAM.
pub fn recv_reset(&mut self, reason: Reason) {
match self.inner {
Closed(..) => {},
_ => {
trace!("recv_reset; reason={:?}", reason);
self.inner = Closed(Cause::Proto(reason));
},
}
}
/// We noticed a protocol error.
pub fn recv_err(&mut self, err: &proto::Error) { pub fn recv_err(&mut self, err: &proto::Error) {
use proto::Error::*; use proto::Error::*;
@@ -216,8 +229,8 @@ impl State {
_ => { _ => {
trace!("recv_err; err={:?}", err); trace!("recv_err; err={:?}", err);
self.inner = Closed(match *err { self.inner = Closed(match *err {
Proto(reason) => Some(Cause::Proto(reason)), Proto(reason) => Cause::LocallyReset(reason),
Io(..) => Some(Cause::Io), Io(..) => Cause::Io,
}); });
}, },
} }
@@ -228,7 +241,7 @@ impl State {
Closed(..) => {}, Closed(..) => {},
s => { s => {
trace!("recv_eof; state={:?}", s); trace!("recv_eof; state={:?}", s);
self.inner = Closed(Some(Cause::Io)); self.inner = Closed(Cause::Io);
} }
} }
} }
@@ -245,28 +258,34 @@ impl State {
}, },
HalfClosedRemote(..) => { HalfClosedRemote(..) => {
trace!("send_close: HalfClosedRemote => Closed"); trace!("send_close: HalfClosedRemote => Closed");
self.inner = Closed(None); self.inner = Closed(Cause::EndStream);
}, },
_ => panic!("transition send_close on unexpected state"), _ => panic!("transition send_close on unexpected state"),
} }
} }
/// Set the stream state to reset /// Set the stream state to reset locally.
pub fn set_reset(&mut self, reason: Reason) { pub fn set_reset(&mut self, reason: Reason) {
self.inner = Closed(Some(Cause::Proto(reason))); self.inner = Closed(Cause::LocallyReset(reason));
} }
/// Set the stream state to canceled /// Set the stream state to canceled
pub fn set_canceled(&mut self) { pub fn set_canceled(&mut self) {
debug_assert!(!self.is_closed()); debug_assert!(!self.is_closed());
self.inner = Closed(Some(Cause::Canceled)); self.inner = Closed(Cause::Canceled);
} }
pub fn is_canceled(&self) -> bool { pub fn is_canceled(&self) -> bool {
use self::Cause::Canceled;
match self.inner { match self.inner {
Closed(Some(Canceled)) => true, Closed(Cause::Canceled) => true,
_ => false,
}
}
pub fn is_local_reset(&self) -> bool {
match self.inner {
Closed(Cause::LocallyReset(_)) => true,
Closed(Cause::Canceled) => true,
_ => false, _ => false,
} }
} }
@@ -274,7 +293,8 @@ impl State {
/// Returns true if the stream is already reset. /// Returns true if the stream is already reset.
pub fn is_reset(&self) -> bool { pub fn is_reset(&self) -> bool {
match self.inner { match self.inner {
Closed(Some(_)) => true, Closed(Cause::EndStream) => false,
Closed(_) => true,
_ => false, _ => false,
} }
} }
@@ -294,10 +314,10 @@ impl State {
pub fn is_send_streaming(&self) -> bool { pub fn is_send_streaming(&self) -> bool {
match self.inner { match self.inner {
Open { Open {
local: Peer::Streaming, local: Streaming,
.. ..
} => true, } => true,
HalfClosedRemote(Peer::Streaming) => true, HalfClosedRemote(Streaming) => true,
_ => false, _ => false,
} }
} }
@@ -319,10 +339,10 @@ impl State {
pub fn is_recv_streaming(&self) -> bool { pub fn is_recv_streaming(&self) -> bool {
match self.inner { match self.inner {
Open { Open {
remote: Peer::Streaming, remote: Streaming,
.. ..
} => true, } => true,
HalfClosedLocal(Peer::Streaming) => true, HalfClosedLocal(Streaming) => true,
_ => false, _ => false,
} }
} }
@@ -353,10 +373,12 @@ impl State {
// TODO: Is this correct? // TODO: Is this correct?
match self.inner { match self.inner {
Closed(Some(Cause::Proto(reason))) => Err(proto::Error::Proto(reason)), Closed(Cause::Proto(reason)) |
Closed(Some(Cause::Canceled)) => Err(proto::Error::Proto(Reason::CANCEL)), Closed(Cause::LocallyReset(reason)) => Err(proto::Error::Proto(reason)),
Closed(Some(Cause::Io)) => Err(proto::Error::Io(io::ErrorKind::BrokenPipe.into())), Closed(Cause::Canceled) => Err(proto::Error::Proto(Reason::CANCEL)),
Closed(None) | HalfClosedRemote(..) => Ok(false), Closed(Cause::Io) => Err(proto::Error::Io(io::ErrorKind::BrokenPipe.into())),
Closed(Cause::EndStream) |
HalfClosedRemote(..) => Ok(false),
_ => Ok(true), _ => Ok(true),
} }
} }
@@ -372,6 +394,6 @@ impl Default for State {
impl Default for Peer { impl Default for Peer {
fn default() -> Self { fn default() -> Self {
Peer::AwaitingHeaders AwaitingHeaders
} }
} }

View File

@@ -289,6 +289,21 @@ where
None None
} }
pub fn pop_if<'a, R, F>(&mut self, store: &'a mut R, f: F) -> Option<store::Ptr<'a>>
where
R: Resolve,
F: Fn(&Stream) -> bool,
{
if let Some(idxs) = self.indices {
let should_pop = f(&store.resolve(idxs.head));
if should_pop {
return self.pop(store);
}
}
None
}
} }
// ===== impl Ptr ===== // ===== impl Ptr =====
@@ -299,6 +314,10 @@ impl<'a> Ptr<'a> {
self.key self.key
} }
pub fn store_mut(&mut self) -> &mut Store {
&mut self.store
}
/// Remove the stream from the store /// Remove the stream from the store
pub fn remove(self) -> StreamId { pub fn remove(self) -> StreamId {
// The stream must have been unlinked before this point // The stream must have been unlinked before this point

View File

@@ -1,5 +1,6 @@
use super::*; use super::*;
use std::time::Instant;
use std::usize; use std::usize;
/// Tracks Stream related state /// Tracks Stream related state
@@ -84,6 +85,12 @@ pub(super) struct Stream {
/// True if the stream is waiting to send a window update /// True if the stream is waiting to send a window update
pub is_pending_window_update: bool, pub is_pending_window_update: bool,
/// The time when this stream may have been locally reset.
pub reset_at: Option<Instant>,
/// Next node in list of reset streams that should expire eventually
pub next_reset_expire: Option<store::Key>,
/// Frames pending for this stream to read /// Frames pending for this stream to read
pub pending_recv: buffer::Deque, pub pending_recv: buffer::Deque,
@@ -120,6 +127,9 @@ pub(super) struct NextWindowUpdate;
#[derive(Debug)] #[derive(Debug)]
pub(super) struct NextOpen; pub(super) struct NextOpen;
#[derive(Debug)]
pub(super) struct NextResetExpire;
impl Stream { impl Stream {
pub fn new( pub fn new(
id: StreamId, id: StreamId,
@@ -167,6 +177,8 @@ impl Stream {
in_flight_recv_data: 0, in_flight_recv_data: 0,
next_window_update: None, next_window_update: None,
is_pending_window_update: false, is_pending_window_update: false,
reset_at: None,
next_reset_expire: None,
pending_recv: buffer::Deque::new(), pending_recv: buffer::Deque::new(),
recv_task: None, recv_task: None,
pending_push_promises: store::Queue::new(), pending_push_promises: store::Queue::new(),
@@ -192,6 +204,12 @@ impl Stream {
!self.is_pending_open && self.state.is_at_least_half_open() !self.is_pending_open && self.state.is_at_least_half_open()
} }
/// Returns true if stream is currently being held for some time because of
/// a local reset.
pub fn is_pending_reset_expiration(&self) -> bool {
self.reset_at.is_some()
}
/// Returns true if the stream is closed /// Returns true if the stream is closed
pub fn is_closed(&self) -> bool { pub fn is_closed(&self) -> bool {
// The state has fully transitioned to closed. // The state has fully transitioned to closed.
@@ -215,7 +233,8 @@ impl Stream {
self.ref_count == 0 && self.ref_count == 0 &&
// The stream is not in any queue // The stream is not in any queue
!self.is_pending_send && !self.is_pending_send_capacity && !self.is_pending_send && !self.is_pending_send_capacity &&
!self.is_pending_accept && !self.is_pending_window_update !self.is_pending_accept && !self.is_pending_window_update &&
!self.reset_at.is_some()
} }
/// Returns true when the consumer of the stream has dropped all handles /// Returns true when the consumer of the stream has dropped all handles
@@ -391,6 +410,32 @@ impl store::Next for NextOpen {
} }
} }
impl store::Next for NextResetExpire {
fn next(stream: &Stream) -> Option<store::Key> {
stream.next_reset_expire
}
fn set_next(stream: &mut Stream, key: Option<store::Key>) {
stream.next_reset_expire = key;
}
fn take_next(stream: &mut Stream) -> Option<store::Key> {
stream.next_reset_expire.take()
}
fn is_queued(stream: &Stream) -> bool {
stream.reset_at.is_some()
}
fn set_queued(stream: &mut Stream, val: bool) {
if val {
stream.reset_at = Some(Instant::now());
} else {
stream.reset_at = None;
}
}
}
// ===== impl ContentLength ===== // ===== impl ContentLength =====
impl ContentLength { impl ContentLength {

View File

@@ -1,11 +1,14 @@
use super::*; use super::{Buffer, Config, Counts, Prioritized, Recv, Send, Stream, StreamId};
use super::store::Resolve; use super::store::{self, Entry, Resolve, Store};
use {client, proto, server}; use {client, proto, server};
use codec::{RecvError, SendError, UserError}; use codec::{Codec, RecvError, SendError, UserError};
use frame::Reason; use frame::{self, Frame, Reason};
use proto::*; use proto::{peer, Peer, WindowSize};
use http::HeaderMap; use bytes::{Buf, Bytes};
use futures::{task, Async, Poll};
use http::{HeaderMap, Request, Response};
use tokio_io::AsyncWrite;
use std::{fmt, io}; use std::{fmt, io};
use std::sync::{Arc, Mutex}; use std::sync::{Arc, Mutex};
@@ -174,7 +177,10 @@ where
let stream = match me.store.find_mut(&id) { let stream = match me.store.find_mut(&id) {
Some(stream) => stream, Some(stream) => stream,
None => return Err(RecvError::Connection(Reason::PROTOCOL_ERROR)), None => {
trace!("recv_data; stream not found: {:?}", id);
return Err(RecvError::Connection(Reason::PROTOCOL_ERROR));
},
}; };
let actions = &mut me.actions; let actions = &mut me.actions;
@@ -364,8 +370,10 @@ where
match me.actions.recv.next_incoming(&mut me.store) { match me.actions.recv.next_incoming(&mut me.store) {
Some(key) => { Some(key) => {
let mut stream = me.store.resolve(key);
trace!("next_incoming; id={:?}, state={:?}", stream.id, stream.state);
// Increment the ref count // Increment the ref count
me.store.resolve(key).ref_inc(); stream.ref_inc();
// Return the key // Return the key
Some(key) Some(key)
@@ -397,6 +405,12 @@ where
me.actions.recv.send_pending_refusal(dst) me.actions.recv.send_pending_refusal(dst)
} }
pub fn clear_expired_reset_streams(&mut self) {
let mut me = self.inner.lock().unwrap();
let me = &mut *me;
me.actions.recv.clear_expired_reset_streams(&mut me.store, &mut me.counts);
}
pub fn poll_complete<T>(&mut self, dst: &mut Codec<T, Prioritized<B>>) -> Poll<(), io::Error> pub fn poll_complete<T>(&mut self, dst: &mut Codec<T, Prioritized<B>>) -> Poll<(), io::Error>
where where
T: AsyncWrite, T: AsyncWrite,
@@ -547,9 +561,10 @@ where
let mut send_buffer = self.send_buffer.inner.lock().unwrap(); let mut send_buffer = self.send_buffer.inner.lock().unwrap();
let send_buffer = &mut *send_buffer; let send_buffer = &mut *send_buffer;
me.counts.transition(stream, |_, stream| { me.counts.transition(stream, |counts, stream| {
actions.send.send_reset( actions.send.send_reset(
reason, send_buffer, stream, &mut actions.task) reason, send_buffer, stream, &mut actions.task);
actions.recv.enqueue_reset_expiration(stream, counts)
}) })
} }
} }
@@ -876,11 +891,12 @@ fn drop_stream_ref(inner: &Mutex<Inner>, key: store::Key) {
let actions = &mut me.actions; let actions = &mut me.actions;
me.counts.transition(stream, |_, mut stream| { me.counts.transition(stream, |counts, mut stream| {
if stream.is_canceled_interest() { if stream.is_canceled_interest() {
actions.send.schedule_cancel( actions.send.schedule_cancel(
&mut stream, &mut stream,
&mut actions.task); &mut actions.task);
actions.recv.enqueue_reset_expiration(stream, counts);
} }
}); });
} }

View File

@@ -3,19 +3,20 @@
use {SendStream, RecvStream, ReleaseCapacity}; use {SendStream, RecvStream, ReleaseCapacity};
use codec::{Codec, RecvError}; use codec::{Codec, RecvError};
use frame::{self, Reason, Settings, StreamId}; use frame::{self, Reason, Settings, StreamId};
use proto::{self, Connection, Prioritized}; use proto::{self, Config, Connection, Prioritized};
use bytes::{Buf, Bytes, IntoBuf}; use bytes::{Buf, Bytes, IntoBuf};
use futures::{self, Async, Future, Poll}; use futures::{self, Async, Future, Poll};
use http::{Request, Response}; use http::{Request, Response};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use std::{convert, fmt, mem}; use std::{convert, fmt, mem};
use std::time::Duration;
/// In progress H2 connection binding /// In progress H2 connection binding
#[must_use = "futures do nothing unless polled"] #[must_use = "futures do nothing unless polled"]
pub struct Handshake<T, B: IntoBuf = Bytes> { pub struct Handshake<T, B: IntoBuf = Bytes> {
/// SETTINGS frame that will be sent once the connection is established. /// The config to pass to Connection::new after handshake succeeds.
settings: Settings, builder: Builder,
/// The current state of the handshake. /// The current state of the handshake.
state: Handshaking<T, B> state: Handshaking<T, B>
} }
@@ -27,8 +28,15 @@ pub struct Server<T, B: IntoBuf> {
} }
/// Build a Server /// Build a Server
#[derive(Clone, Debug, Default)] #[derive(Clone, Debug)]
pub struct Builder { pub struct Builder {
/// Time to keep locally reset streams around before reaping.
reset_stream_duration: Duration,
/// Maximum number of locally reset streams to keep at a time.
reset_stream_max: usize,
/// Initial `Settings` frame to send as part of the handshake.
settings: Settings, settings: Settings,
} }
@@ -95,23 +103,23 @@ where
B: IntoBuf, B: IntoBuf,
B::Buf: 'static, B::Buf: 'static,
{ {
fn handshake2(io: T, settings: Settings) -> Handshake<T, B> { fn handshake2(io: T, builder: Builder) -> Handshake<T, B> {
// Create the codec. // Create the codec.
let mut codec = Codec::new(io); let mut codec = Codec::new(io);
if let Some(max) = settings.max_frame_size() { if let Some(max) = builder.settings.max_frame_size() {
codec.set_max_recv_frame_size(max as usize); codec.set_max_recv_frame_size(max as usize);
} }
// Send initial settings frame. // Send initial settings frame.
codec codec
.buffer(settings.clone().into()) .buffer(builder.settings.clone().into())
.expect("invalid SETTINGS frame"); .expect("invalid SETTINGS frame");
// Create the handshake future. // Create the handshake future.
let state = Handshaking::from(codec); let state = Handshaking::from(codec);
Handshake { settings, state } Handshake { builder, state }
} }
/// Sets the target window size for the whole connection. /// Sets the target window size for the whole connection.
@@ -204,6 +212,26 @@ impl Builder {
self self
} }
/// Set the maximum number of concurrent locally reset streams.
///
/// Locally reset streams are to "ignore frames from the peer for some
/// time". While waiting for that time, locally reset streams "waste"
/// space in order to be able to ignore those frames. This setting
/// can limit how many extra streams are left waiting for "some time".
pub fn max_concurrent_reset_streams(&mut self, max: usize) -> &mut Self {
self.reset_stream_max = max;
self
}
/// Set the maximum number of concurrent locally reset streams.
///
/// Locally reset streams are to "ignore frames from the peer for some
/// time", but that time is unspecified. Set that time with this setting.
pub fn reset_stream_duration(&mut self, dur: Duration) -> &mut Self {
self.reset_stream_duration = dur;
self
}
/// Bind an H2 server connection. /// Bind an H2 server connection.
/// ///
/// Returns a future which resolves to the connection value once the H2 /// Returns a future which resolves to the connection value once the H2
@@ -214,7 +242,17 @@ impl Builder {
B: IntoBuf, B: IntoBuf,
B::Buf: 'static, B::Buf: 'static,
{ {
Server::handshake2(io, self.settings.clone()) Server::handshake2(io, self.clone())
}
}
impl Default for Builder {
fn default() -> Builder {
Builder {
reset_stream_duration: Duration::from_secs(proto::DEFAULT_RESET_STREAM_SECS),
reset_stream_max: proto::DEFAULT_RESET_STREAM_MAX,
settings: Settings::default(),
}
} }
} }
@@ -357,8 +395,12 @@ impl<T, B: IntoBuf> Future for Handshake<T, B>
unreachable!("Handshake::poll() state was not advanced completely!") unreachable!("Handshake::poll() state was not advanced completely!")
}; };
let server = poll?.map(|codec| { let server = poll?.map(|codec| {
let connection = let connection = Connection::new(codec, Config {
Connection::new(codec, &self.settings, 2.into()); next_stream_id: 2.into(),
reset_stream_duration: self.builder.reset_stream_duration,
reset_stream_max: self.builder.reset_stream_max,
settings: self.builder.settings.clone(),
});
trace!("Handshake::poll(); connection established!"); trace!("Handshake::poll(); connection established!");
Server { connection } Server { connection }
}); });

View File

@@ -276,17 +276,14 @@ fn recv_data_overflows_stream_window() {
.send_frame(frames::data(1, vec![0u8; 16_384])) .send_frame(frames::data(1, vec![0u8; 16_384]))
// this frame overflows the window! // this frame overflows the window!
.send_frame(frames::data(1, &[0; 16][..]).eos()) .send_frame(frames::data(1, &[0; 16][..]).eos())
// expecting goaway for the conn .recv_frame(frames::reset(1).flow_control())
// TODO: change to a RST_STREAM eventually .close();
.recv_frame(frames::go_away(0).flow_control())
// close the connection
.map(drop);
let h2 = Client::builder() let h2 = Client::builder()
.initial_window_size(16_384) .initial_window_size(16_384)
.handshake::<_, Bytes>(io) .handshake::<_, Bytes>(io)
.unwrap() .unwrap()
.and_then(|(mut client, h2)| { .and_then(|(mut client, conn)| {
let request = Request::builder() let request = Request::builder()
.method(Method::GET) .method(Method::GET)
.uri("https://http2.akamai.com/") .uri("https://http2.akamai.com/")
@@ -310,15 +307,6 @@ fn recv_data_overflows_stream_window() {
}) })
}); });
// client should see a flow control error
let conn = h2.then(|res| {
let err = res.unwrap_err();
assert_eq!(
err.to_string(),
"protocol error: flow-control protocol violated"
);
Ok::<(), ()>(())
});
conn.unwrap().join(req) conn.unwrap().join(req)
}); });
h2.join(mock).wait().unwrap(); h2.join(mock).wait().unwrap();

View File

@@ -465,6 +465,209 @@ fn skipped_stream_ids_are_implicitly_closed() {
h2.join(srv).wait().expect("wait"); h2.join(srv).wait().expect("wait");
} }
#[test]
fn send_rst_stream_allows_recv_frames() {
let _ = ::env_logger::init();
let (io, srv) = mock::new();
let srv = srv.assert_client_handshake()
.unwrap()
.recv_settings()
.recv_frame(
frames::headers(1)
.request("GET", "https://example.com/")
.eos(),
)
.send_frame(frames::headers(1).response(200))
.recv_frame(frames::reset(1).cancel())
// sending frames after canceled!
// note: sending 2 to cosume 50% of connection window
.send_frame(frames::data(1, vec![0; 16_384]))
.send_frame(frames::data(1, vec![0; 16_384]).eos())
// make sure we automatically free the connection window
.recv_frame(frames::window_update(0, 16_384 * 2))
// do a pingpong to ensure no other frames were sent
.ping_pong([1; 8])
.close();
let client = Client::handshake(io)
.expect("handshake")
.and_then(|(mut client, conn)| {
let request = Request::builder()
.method(Method::GET)
.uri("https://example.com/")
.body(())
.unwrap();
let req = client.send_request(request, true)
.unwrap()
.0.expect("response")
.and_then(|resp| {
assert_eq!(resp.status(), StatusCode::OK);
// drop resp will send a reset
Ok(())
});
conn.expect("client")
.drive(req)
.and_then(|(conn, _)| conn)
});
client.join(srv).wait().expect("wait");
}
#[test]
fn rst_stream_expires() {
let _ = ::env_logger::init();
let (io, srv) = mock::new();
let srv = srv.assert_client_handshake()
.unwrap()
.recv_settings()
.recv_frame(
frames::headers(1)
.request("GET", "https://example.com/")
.eos(),
)
.send_frame(frames::headers(1).response(200))
.send_frame(frames::data(1, vec![0; 16_384]))
.recv_frame(frames::reset(1).cancel())
// wait till after the configured duration
.idle_ms(15)
.ping_pong([1; 8])
// sending frame after canceled!
.send_frame(frames::data(1, vec![0; 16_384]).eos())
.recv_frame(frames::go_away(0).protocol_error())
.close();
let client = Client::builder()
.reset_stream_duration(Duration::from_millis(10))
.handshake::<_, Bytes>(io)
.expect("handshake")
.and_then(|(mut client, conn)| {
let request = Request::builder()
.method(Method::GET)
.uri("https://example.com/")
.body(())
.unwrap();
let req = client.send_request(request, true)
.unwrap()
.0.expect("response")
.and_then(|resp| {
assert_eq!(resp.status(), StatusCode::OK);
// drop resp will send a reset
Ok(())
})
.map_err(|()| -> Error {
unreachable!()
});
conn.drive(req)
.and_then(|(conn, _)| conn.expect_err("client"))
.map(|err| {
assert_eq!(
err.to_string(),
"protocol error: unspecific protocol error detected"
);
})
});
client.join(srv).wait().expect("wait");
}
#[test]
fn rst_stream_max() {
let _ = ::env_logger::init();
let (io, srv) = mock::new();
let srv = srv.assert_client_handshake()
.unwrap()
.recv_settings()
.recv_frame(
frames::headers(1)
.request("GET", "https://example.com/")
.eos(),
)
.recv_frame(
frames::headers(3)
.request("GET", "https://example.com/")
.eos(),
)
.send_frame(frames::headers(1).response(200))
.send_frame(frames::data(1, vec![0; 16]))
.send_frame(frames::headers(3).response(200))
.send_frame(frames::data(3, vec![0; 16]))
.recv_frame(frames::reset(1).cancel())
.recv_frame(frames::reset(3).cancel())
// sending frame after canceled!
// newer streams trump older streams
// 3 is still being ignored
.send_frame(frames::data(3, vec![0; 16]).eos())
// ping pong to be sure of no goaway
.ping_pong([1; 8])
// 1 has been evicted, will get a goaway
.send_frame(frames::data(1, vec![0; 16]).eos())
.recv_frame(frames::go_away(0).protocol_error())
.close();
let client = Client::builder()
.max_concurrent_reset_streams(1)
.handshake::<_, Bytes>(io)
.expect("handshake")
.and_then(|(mut client, conn)| {
let request = Request::builder()
.method(Method::GET)
.uri("https://example.com/")
.body(())
.unwrap();
let req1 = client.send_request(request, true)
.unwrap()
.0.expect("response1")
.and_then(|resp| {
assert_eq!(resp.status(), StatusCode::OK);
// drop resp will send a reset
Ok(())
})
.map_err(|()| -> Error {
unreachable!()
});
let request = Request::builder()
.method(Method::GET)
.uri("https://example.com/")
.body(())
.unwrap();
let req2 = client.send_request(request, true)
.unwrap()
.0.expect("response2")
.and_then(|resp| {
assert_eq!(resp.status(), StatusCode::OK);
// drop resp will send a reset
Ok(())
})
.map_err(|()| -> Error {
unreachable!()
});
conn.drive(req1.join(req2))
.and_then(|(conn, _)| conn.expect_err("client"))
.map(|err| {
assert_eq!(
err.to_string(),
"protocol error: unspecific protocol error detected"
);
})
});
client.join(srv).wait().expect("wait");
}
/* /*
#[test] #[test]
fn send_data_after_headers_eos() { fn send_data_after_headers_eos() {

View File

@@ -109,11 +109,11 @@ where
type Error = (); type Error = ();
fn poll(&mut self) -> Poll<T::Error, ()> { fn poll(&mut self) -> Poll<T::Error, ()> {
let poll = match self.inner.poll() {
self.inner.poll() Ok(Async::Ready(v)) => panic!("Future::unwrap_err() on an Ok value: {:?}", v),
.map_err(Async::Ready) Ok(Async::NotReady) => Ok(Async::NotReady),
.unwrap_err(); Err(e) => Ok(Async::Ready(e)),
Ok(poll) }
} }
} }
@@ -159,11 +159,11 @@ where
type Error = (); type Error = ();
fn poll(&mut self) -> Poll<T::Error, ()> { fn poll(&mut self) -> Poll<T::Error, ()> {
let poll = match self.inner.poll() {
self.inner.poll() Ok(Async::Ready(v)) => panic!("{}: {:?}", self.msg, v),
.map_err(Async::Ready) Ok(Async::NotReady) => Ok(Async::NotReady),
.expect_err(&self.msg); Err(e) => Ok(Async::Ready(e)),
Ok(poll) }
} }
} }

View File

@@ -1,4 +1,4 @@
use {FutureExt, SendFrame}; use {frames, FutureExt, SendFrame};
use h2::{self, RecvError, SendError}; use h2::{self, RecvError, SendError};
use h2::frame::{self, Frame}; use h2::frame::{self, Frame};
@@ -441,6 +441,15 @@ pub trait HandleFutureExt {
} }
} }
fn ping_pong(self, payload: [u8; 8]) -> RecvFrame<<SendFrameFut<Self> as IntoRecvFrame>::Future>
where
Self: Future<Item=Handle> + Sized + 'static,
Self::Error: fmt::Debug,
{
self.send_frame(frames::ping(payload))
.recv_frame(frames::ping(payload).pong())
}
fn idle_ms(self, ms: usize) -> Box<Future<Item = Handle, Error = Self::Error>> fn idle_ms(self, ms: usize) -> Box<Future<Item = Handle, Error = Self::Error>>
where where
Self: Sized + 'static, Self: Sized + 'static,