wire up remote settings application

This commit is contained in:
Oliver Gould
2017-07-15 21:01:11 +00:00
parent 1ed4b7e56a
commit 59c92e1089
11 changed files with 292 additions and 88 deletions

View File

@@ -23,6 +23,10 @@ impl SettingSet {
pub fn initial_window_size(&self) -> u32 { pub fn initial_window_size(&self) -> u32 {
self.initial_window_size.unwrap_or(65_535) self.initial_window_size.unwrap_or(65_535)
} }
pub fn max_concurrent_streams(&self) -> Option<u32> {
self.max_concurrent_streams
}
} }
/// An enum that lists all valid settings that can be sent in a SETTINGS /// An enum that lists all valid settings that can be sent in a SETTINGS

View File

@@ -101,11 +101,11 @@ impl<T, P, B: IntoBuf> Connection<T, P, B> {
assert!(self.sending_window_update.is_none()); assert!(self.sending_window_update.is_none());
let added = if id.is_zero() { let added = if id.is_zero() {
self.local_flow_controller.increment_window_size(incr); self.local_flow_controller.grow_window(incr);
self.local_flow_controller.take_window_update() self.local_flow_controller.take_window_update()
} else { } else {
self.streams.get_mut(&id).and_then(|s| { self.streams.get_mut(&id).and_then(|s| {
s.increment_recv_window_size(incr); s.grow_recv_window(incr);
s.take_recv_window_update() s.take_recv_window_update()
}) })
}; };
@@ -126,10 +126,10 @@ impl<T, P, B: IntoBuf> Connection<T, P, B> {
} }
let added = if id.is_zero() { let added = if id.is_zero() {
self.remote_flow_controller.increment_window_size(incr); self.remote_flow_controller.grow_window(incr);
true true
} else if let Some(mut s) = self.streams.get_mut(&id) { } else if let Some(mut s) = self.streams.get_mut(&id) {
s.increment_send_window_size(incr); s.grow_send_window(incr);
true true
} else { } else {
false false

View File

@@ -1,12 +1,14 @@
use ConnectionError; use ConnectionError;
use frame::{self, Frame}; use frame::{self, Frame};
use proto::{ReadySink, StreamMap, StreamTransporter, WindowSize}; use proto::{ReadySink, StreamMap, ConnectionTransporter, StreamTransporter};
use futures::*; use futures::*;
#[derive(Debug)] #[derive(Debug)]
pub struct FlowControl<T> { pub struct FlowControl<T> {
inner: T, inner: T,
initial_local_window_size: u32,
initial_remote_window_size: u32,
} }
impl<T, U> FlowControl<T> impl<T, U> FlowControl<T>
@@ -14,8 +16,85 @@ impl<T, U> FlowControl<T>
T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>, T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
T: StreamTransporter T: StreamTransporter
{ {
pub fn new(inner: T) -> FlowControl<T> { pub fn new(initial_local_window_size: u32,
FlowControl { inner } initial_remote_window_size: u32,
inner: T)
-> FlowControl<T>
{
FlowControl {
inner,
initial_local_window_size,
initial_remote_window_size,
}
}
}
/// Applies an update to an endpoint's initial window size.
///
/// Per RFC 7540 §6.9.2
///
/// > In addition to changing the flow-control window for streams that are not yet
/// > active, a SETTINGS frame can alter the initial flow-control window size for
/// > streams with active flow-control windows (that is, streams in the "open" or
/// > "half-closed (remote)" state). When the value of SETTINGS_INITIAL_WINDOW_SIZE
/// > changes, a receiver MUST adjust the size of all stream flow-control windows that
/// > it maintains by the difference between the new value and the old value.
/// >
/// > A change to `SETTINGS_INITIAL_WINDOW_SIZE` can cause the available space in a
/// > flow-control window to become negative. A sender MUST track the negative
/// > flow-control window and MUST NOT send new flow-controlled frames until it
/// > receives WINDOW_UPDATE frames that cause the flow-control window to become
/// > positive.
impl<T> ConnectionTransporter for FlowControl<T>
where T: ConnectionTransporter,
T: StreamTransporter
{
fn apply_local_settings(&mut self, set: &frame::SettingSet) -> Result<(), ConnectionError> {
self.inner.apply_local_settings(set)?;
let old_window_size = self.initial_local_window_size;
let new_window_size = set.initial_window_size();
if new_window_size == old_window_size {
return Ok(());
}
{
let mut streams = self.streams_mut();
if new_window_size < old_window_size {
let decr = old_window_size - new_window_size;
streams.shrink_local_window(decr);
} else {
let incr = new_window_size - old_window_size;
streams.grow_local_window(incr);
}
}
self.initial_local_window_size = new_window_size;
Ok(())
}
fn apply_remote_settings(&mut self, set: &frame::SettingSet) -> Result<(), ConnectionError> {
self.inner.apply_remote_settings(set)?;
let old_window_size = self.initial_remote_window_size;
let new_window_size = set.initial_window_size();
if new_window_size == old_window_size {
return Ok(());
}
{
let mut streams = self.streams_mut();
if new_window_size < old_window_size {
let decr = old_window_size - new_window_size;
streams.shrink_remote_window(decr);
} else {
let incr = new_window_size - old_window_size;
streams.grow_remote_window(incr);
}
}
self.initial_remote_window_size = new_window_size;
Ok(())
} }
} }

View File

@@ -52,7 +52,7 @@ impl FlowController {
} }
/// Applies a window increment immediately. /// Applies a window increment immediately.
pub fn increment_window_size(&mut self, sz: WindowSize) { pub fn grow_window(&mut self, sz: WindowSize) {
if sz <= self.underflow { if sz <= self.underflow {
self.underflow -= sz; self.underflow -= sz;
return; return;

View File

@@ -1,7 +1,7 @@
use {hpack, ConnectionError}; use {hpack, ConnectionError};
use frame::{self, Frame, Kind}; use frame::{self, Frame, Kind};
use frame::DEFAULT_SETTINGS_HEADER_TABLE_SIZE; use frame::DEFAULT_SETTINGS_HEADER_TABLE_SIZE;
use proto::ReadySink; use proto::{ConnectionTransporter, ReadySink};
use futures::*; use futures::*;
@@ -103,6 +103,16 @@ impl<T> FramedRead<T> {
} }
} }
impl<T: ConnectionTransporter> ConnectionTransporter for FramedRead<T> {
fn apply_local_settings(&mut self, set: &frame::SettingSet) -> Result<(), ConnectionError> {
self.inner.get_mut().apply_local_settings(set)
}
fn apply_remote_settings(&mut self, set: &frame::SettingSet) -> Result<(), ConnectionError> {
self.inner.get_mut().apply_remote_settings(set)
}
}
impl<T> Stream for FramedRead<T> impl<T> Stream for FramedRead<T>
where T: AsyncRead, where T: AsyncRead,
{ {

View File

@@ -1,6 +1,6 @@
use {hpack, ConnectionError, FrameSize}; use {hpack, ConnectionError, FrameSize};
use frame::{self, Frame}; use frame::{self, Frame};
use proto::ReadySink; use proto::{ConnectionTransporter, ReadySink};
use futures::*; use futures::*;
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
@@ -78,6 +78,16 @@ impl<T, B> FramedWrite<T, B>
} }
} }
impl<T, B> ConnectionTransporter for FramedWrite<T, B> {
fn apply_local_settings(&mut self, _set: &frame::SettingSet) -> Result<(), ConnectionError> {
Ok(())
}
fn apply_remote_settings(&mut self, _set: &frame::SettingSet) -> Result<(), ConnectionError> {
Ok(())
}
}
impl<T, B> Sink for FramedWrite<T, B> impl<T, B> Sink for FramedWrite<T, B>
where T: AsyncWrite, where T: AsyncWrite,
B: Buf, B: Buf,

View File

@@ -20,7 +20,7 @@ pub use self::settings::Settings;
pub use self::stream_tracker::StreamTracker; pub use self::stream_tracker::StreamTracker;
use self::state::StreamState; use self::state::StreamState;
use {frame, Peer, StreamId}; use {frame, ConnectionError, Peer, StreamId};
use tokio_io::{AsyncRead, AsyncWrite}; use tokio_io::{AsyncRead, AsyncWrite};
use tokio_io::codec::length_delimited; use tokio_io::codec::length_delimited;
@@ -31,7 +31,20 @@ use ordermap::OrderMap;
use fnv::FnvHasher; use fnv::FnvHasher;
use std::hash::BuildHasherDefault; use std::hash::BuildHasherDefault;
/// Represents /// Represents the internals of an HTTP2 connection.
///
/// A transport consists of several layers (_transporters_) and is arranged from _top_
/// (near the application) to _bottom_ (near the network). Each transporter implements a
/// Stream of frames received from the remote, and a ReadySink of frames sent to the
/// remote.
///
/// At the top of the transport, the Settings module is responsible for:
/// - Transmitting local settings to the remote.
/// - Sending settings acknowledgements for all settings frames received from the remote.
/// - Exposing settings upward to the Connection.
///
/// All transporters below Settings must apply relevant settings before passing a frame on
/// to another level. For example, if the frame writer n
type Transport<T, B> = type Transport<T, B> =
Settings< Settings<
FlowControl< FlowControl<
@@ -44,15 +57,46 @@ type Framer<T, B> =
FramedRead< FramedRead<
FramedWrite<T, B>>; FramedWrite<T, B>>;
pub type WindowSize = u32; pub type WindowSize = u32;
#[derive(Debug)] #[derive(Debug, Default)]
struct StreamMap { pub struct StreamMap {
inner: OrderMap<StreamId, StreamState, BuildHasherDefault<FnvHasher>> inner: OrderMap<StreamId, StreamState, BuildHasherDefault<FnvHasher>>
} }
trait StreamTransporter { impl StreamMap {
fn shrink_local_window(&mut self, decr: u32) {
for (_, mut s) in &mut self.inner {
s.shrink_recv_window(decr)
}
}
fn grow_local_window(&mut self, incr: u32) {
for (_, mut s) in &mut self.inner {
s.grow_recv_window(incr)
}
}
fn shrink_remote_window(&mut self, decr: u32) {
for (_, mut s) in &mut self.inner {
s.shrink_send_window(decr)
}
}
fn grow_remote_window(&mut self, incr: u32) {
for (_, mut s) in &mut self.inner {
s.grow_send_window(incr)
}
}
}
/// Allows settings to be applied from the top of the stack to the lower levels.d
pub trait ConnectionTransporter {
fn apply_local_settings(&mut self, set: &frame::SettingSet) -> Result<(), ConnectionError>;
fn apply_remote_settings(&mut self, set: &frame::SettingSet) -> Result<(), ConnectionError>;
}
pub trait StreamTransporter {
fn streams(&self)-> &StreamMap; fn streams(&self)-> &StreamMap;
fn streams_mut(&mut self) -> &mut StreamMap; fn streams_mut(&mut self) -> &mut StreamMap;
} }
@@ -70,6 +114,8 @@ pub fn from_io<T, P, B>(io: T, settings: frame::SettingSet)
// To avoid code duplication, we're going to go this route. It is a bit // To avoid code duplication, we're going to go this route. It is a bit
// weird, but oh well... // weird, but oh well...
//
// We first create a Settings directly around a framed writer
let settings = Settings::new( let settings = Settings::new(
framed_write, settings); framed_write, settings);
@@ -92,30 +138,32 @@ pub fn server_handshaker<T, B>(io: T, settings: frame::SettingSet)
} }
/// Create a full H2 transport from the server handshaker /// Create a full H2 transport from the server handshaker
pub fn from_server_handshaker<T, P, B>(transport: Settings<FramedWrite<T, B::Buf>>) pub fn from_server_handshaker<T, P, B>(settings: Settings<FramedWrite<T, B::Buf>>)
-> Connection<T, P, B> -> Connection<T, P, B>
where T: AsyncRead + AsyncWrite, where T: AsyncRead + AsyncWrite,
P: Peer, P: Peer,
B: IntoBuf, B: IntoBuf,
{ {
let settings = transport.swap_inner(|io| { let initial_local_window_size = settings.local_settings().initial_window_size();
// Delimit the frames let initial_remote_window_size = settings.remote_settings().initial_window_size();
let framed_read = length_delimited::Builder::new() let local_max_concurrency = settings.local_settings().max_concurrent_streams();
let remote_max_concurrency = settings.remote_settings().max_concurrent_streams();
// Replace Settings' writer with a full transport.
let transport = settings.swap_inner(|io| {
// Delimit the frames.
let framer = length_delimited::Builder::new()
.big_endian() .big_endian()
.length_field_length(3) .length_field_length(3)
.length_adjustment(9) .length_adjustment(9)
.num_skip(0) // Don't skip the header .num_skip(0) // Don't skip the header
.new_read(io); .new_read(io);
// Map to `Frame` types FlowControl::new(initial_local_window_size, initial_remote_window_size,
let framed = FramedRead::new(framed_read); StreamTracker::new(local_max_concurrency, remote_max_concurrency,
FlowControl::new(
StreamTracker::new(
PingPong::new( PingPong::new(
framed))) FramedRead::new(framer))))
}); });
// Finally, return the constructed `Connection` connection::new(transport)
connection::new(settings)
} }

View File

@@ -1,7 +1,7 @@
use ConnectionError; use ConnectionError;
use frame::{Frame, Ping}; use frame::{Frame, Ping, SettingSet};
use futures::*; use futures::*;
use proto::ReadySink; use proto::{ConnectionTransporter, ReadySink};
/// Acknowledges ping requests from the remote. /// Acknowledges ping requests from the remote.
#[derive(Debug)] #[derive(Debug)]
@@ -22,6 +22,16 @@ impl<T, U> PingPong<T, U>
} }
} }
impl<T: ConnectionTransporter, U> ConnectionTransporter for PingPong<T, U> {
fn apply_local_settings(&mut self, set: &SettingSet) -> Result<(), ConnectionError> {
self.inner.apply_local_settings(set)
}
fn apply_remote_settings(&mut self, set: &SettingSet) -> Result<(), ConnectionError> {
self.inner.apply_remote_settings(set)
}
}
impl<T, U> PingPong<T, U> impl<T, U> PingPong<T, U>
where T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>, where T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
{ {

View File

@@ -1,6 +1,6 @@
use ConnectionError; use ConnectionError;
use frame::{self, Frame}; use frame::{self, Frame};
use proto::ReadySink; use proto::{ConnectionTransporter, ReadySink, StreamMap, StreamTransporter};
use futures::*; use futures::*;
use tokio_io::AsyncRead; use tokio_io::AsyncRead;
@@ -94,9 +94,20 @@ impl<T, U> Settings<T>
} }
} }
impl<T: StreamTransporter> StreamTransporter for Settings<T> {
fn streams(&self) -> &StreamMap {
self.inner.streams()
}
fn streams_mut(&mut self) -> &mut StreamMap {
self.inner.streams_mut()
}
}
impl<T, U> Stream for Settings<T> impl<T, U> Stream for Settings<T>
where T: Stream<Item = Frame, Error = ConnectionError>, where T: Stream<Item = Frame, Error = ConnectionError>,
T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>, T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
T: ConnectionTransporter,
{ {
type Item = Frame; type Item = Frame;
type Error = ConnectionError; type Error = ConnectionError;
@@ -112,8 +123,10 @@ impl<T, U> Stream for Settings<T>
// Received new settings, queue an ACK // Received new settings, queue an ACK
self.remaining_acks += 1; self.remaining_acks += 1;
// Save off the settings // Apply the settings before saving them.
self.remote = v.into_set(); let settings = v.into_set();
self.inner.apply_remote_settings(&settings)?;
self.remote = settings;
let _ = try!(self.try_send_pending()); let _ = try!(self.try_send_pending());
} }

View File

@@ -47,8 +47,9 @@ use proto::FlowController;
#[derive(Debug, Copy, Clone)] #[derive(Debug, Copy, Clone)]
pub enum StreamState { pub enum StreamState {
Idle, Idle,
ReservedLocal, // TODO: these states shouldn't count against concurrency limits:
ReservedRemote, //ReservedLocal,
//ReservedRemote,
Open { Open {
local: PeerState, local: PeerState,
remote: PeerState, remote: PeerState,
@@ -65,7 +66,7 @@ impl StreamState {
/// caller should send the the returned window size increment to the remote. /// caller should send the the returned window size increment to the remote.
/// ///
/// If the remote is closed, None is returned. /// If the remote is closed, None is returned.
pub fn increment_send_window_size(&mut self, incr: u32) { pub fn grow_send_window(&mut self, incr: u32) {
use self::StreamState::*; use self::StreamState::*;
use self::PeerState::*; use self::PeerState::*;
@@ -75,11 +76,27 @@ impl StreamState {
match self { match self {
&mut Open { remote: Data(ref mut fc), .. } | &mut Open { remote: Data(ref mut fc), .. } |
&mut HalfClosedLocal(Data(ref mut fc)) => fc.increment_window_size(incr), &mut HalfClosedLocal(Data(ref mut fc)) => fc.grow_window(incr),
_ => {}, _ => {},
} }
} }
pub fn shrink_send_window(&mut self, decr: u32) {
use self::StreamState::*;
use self::PeerState::*;
if decr == 0 {
return;
}
match self {
&mut Open { local: Data(ref mut fc), .. } |
&mut HalfClosedLocal(Data(ref mut fc)) => fc.shrink_window(decr),
_ => {},
}
}
/// Consumes newly-advertised capacity to inform the local endpoint it may send more /// Consumes newly-advertised capacity to inform the local endpoint it may send more
/// data. /// data.
pub fn take_send_window_update(&mut self) -> Option<u32> { pub fn take_send_window_update(&mut self) -> Option<u32> {
@@ -98,7 +115,7 @@ impl StreamState {
/// ///
/// Returns the amount of capacity created, accounting for window size changes. The /// Returns the amount of capacity created, accounting for window size changes. The
/// caller should send the the returned window size increment to the remote. /// caller should send the the returned window size increment to the remote.
pub fn increment_recv_window_size(&mut self, incr: u32) { pub fn grow_recv_window(&mut self, incr: u32) {
use self::StreamState::*; use self::StreamState::*;
use self::PeerState::*; use self::PeerState::*;
@@ -108,7 +125,22 @@ impl StreamState {
match self { match self {
&mut Open { local: Data(ref mut fc), .. } | &mut Open { local: Data(ref mut fc), .. } |
&mut HalfClosedRemote(Data(ref mut fc)) => fc.increment_window_size(incr), &mut HalfClosedRemote(Data(ref mut fc)) => fc.grow_window(incr),
_ => {},
}
}
pub fn shrink_recv_window(&mut self, decr: u32) {
use self::StreamState::*;
use self::PeerState::*;
if decr == 0 {
return;
}
match self {
&mut Open { local: Data(ref mut fc), .. } |
&mut HalfClosedRemote(Data(ref mut fc)) => fc.shrink_window(decr),
_ => {}, _ => {},
} }
} }
@@ -126,46 +158,6 @@ impl StreamState {
} }
} }
/// Applies an update to the remote's initial window size.
///
/// Per RFC 7540 §6.9.2
///
/// > In addition to changing the flow-control window for streams that are not yet
/// > active, a SETTINGS frame can alter the initial flow-control window size for
/// > streams with active flow-control windows (that is, streams in the "open" or
/// > "half-closed (remote)" state). When the value of SETTINGS_INITIAL_WINDOW_SIZE
/// > changes, a receiver MUST adjust the size of all stream flow-control windows that
/// > it maintains by the difference between the new value and the old value.
/// >
/// > A change to `SETTINGS_INITIAL_WINDOW_SIZE` can cause the available space in a
/// > flow-control window to become negative. A sender MUST track the negative
/// > flow-control window and MUST NOT send new flow-controlled frames until it
/// > receives WINDOW_UPDATE frames that cause the flow-control window to become
/// > positive.
pub fn update_initial_recv_window_size(&mut self, old: u32, new: u32) {
use self::StreamState::*;
use self::PeerState::*;
match self {
&mut Open { remote: Data(ref mut fc), .. } |
&mut HalfClosedLocal(Data(ref mut fc)) => {
if new < old {
fc.shrink_window(old - new);
} else {
fc.increment_window_size(new - old);
}
}
_ => {}
}
}
/// TODO Connection doesn't have an API for local updates yet.
pub fn update_initial_send_window_size(&mut self, _old: u32, _new: u32) {
//use self::StreamState::*;
//use self::PeerState::*;
unimplemented!()
}
/// Transition the state to represent headers being received. /// Transition the state to represent headers being received.
/// ///
/// Returns true if this state transition results in iniitializing the /// Returns true if this state transition results in iniitializing the
@@ -212,8 +204,6 @@ impl StreamState {
Closed | HalfClosedRemote(..) => { Closed | HalfClosedRemote(..) => {
Err(ProtocolError.into()) Err(ProtocolError.into())
} }
_ => unimplemented!(),
} }
} }
@@ -301,8 +291,6 @@ impl StreamState {
Closed | HalfClosedLocal(..) => { Closed | HalfClosedLocal(..) => {
Err(UnexpectedFrameType.into()) Err(UnexpectedFrameType.into())
} }
_ => unimplemented!(),
} }
} }

View File

@@ -1,30 +1,72 @@
use ConnectionError; use ConnectionError;
use frame::{self, Frame}; use frame::{self, Frame};
use proto::{ReadySink, StreamMap, StreamTransporter, WindowSize}; use proto::{ReadySink, StreamMap, ConnectionTransporter, StreamTransporter};
use futures::*; use futures::*;
#[derive(Debug)] #[derive(Debug)]
pub struct StreamTracker<T> { pub struct StreamTracker<T> {
inner: T, inner: T,
streams: StreamMap,
local_max_concurrency: Option<u32>,
remote_max_concurrency: Option<u32>,
} }
impl<T, U> StreamTracker<T> impl<T, U> StreamTracker<T>
where T: Stream<Item = Frame, Error = ConnectionError>, where T: Stream<Item = Frame, Error = ConnectionError>,
T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError> T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>
{ {
pub fn new(inner: T) -> StreamTracker<T> { pub fn new(local_max_concurrency: Option<u32>,
StreamTracker { inner } remote_max_concurrency: Option<u32>,
inner: T)
-> StreamTracker<T>
{
StreamTracker {
inner,
streams: StreamMap::default(),
local_max_concurrency,
remote_max_concurrency,
}
} }
} }
impl<T> StreamTransporter for StreamTracker<T> { impl<T> StreamTransporter for StreamTracker<T> {
fn streams(&self) -> &StreamMap { fn streams(&self) -> &StreamMap {
unimplemented!() &self.streams
} }
fn streams_mut(&mut self) -> &mut StreamMap { fn streams_mut(&mut self) -> &mut StreamMap {
unimplemented!() &mut self.streams
}
}
/// Handles updates to `SETTINGS_MAX_CONCURRENT_STREAMS`.
///
/// > Indicates the maximum number of concurrent streams that the sender will allow. This
/// > limit is directional: it applies to the number of streams that the sender permits the
/// > receiver to create. Initially, there is no limit to this value. It is recommended that
/// > this value be no smaller than 100, so as to not unnecessarily limit parallelism.
/// >
/// > A value of 0 for SETTINGS_MAX_CONCURRENT_STREAMS SHOULD NOT be treated as special by
/// > endpoints. A zero value does prevent the creation of new streams; however, this can
/// > also happen for any limit that is exhausted with active streams. Servers SHOULD only
/// > set a zero value for short durations; if a server does not wish to accept requests,
/// > closing the connection is more appropriate.
///
/// > An endpoint that wishes to reduce the value of SETTINGS_MAX_CONCURRENT_STREAMS to a
/// > value that is below the current number of open streams can either close streams that
/// > exceed the new value or allow streams to complete.
///
/// This module does NOT close streams when the setting changes.
impl<T: ConnectionTransporter> ConnectionTransporter for StreamTracker<T> {
fn apply_local_settings(&mut self, set: &frame::SettingSet) -> Result<(), ConnectionError> {
self.local_max_concurrency = set.max_concurrent_streams();
self.inner.apply_local_settings(set)
}
fn apply_remote_settings(&mut self, set: &frame::SettingSet) -> Result<(), ConnectionError> {
self.remote_max_concurrency = set.max_concurrent_streams();
self.inner.apply_remote_settings(set)
} }
} }