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:
@@ -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,
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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 {
|
||||||
|
|||||||
@@ -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);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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 }
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -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();
|
||||||
|
|||||||
@@ -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() {
|
||||||
|
|||||||
@@ -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)
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
Reference in New Issue
Block a user