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 {
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

View File

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

View File

@@ -1,12 +1,14 @@
use ConnectionError;
use frame::{self, Frame};
use proto::{ReadySink, StreamMap, StreamTransporter, WindowSize};
use proto::{ReadySink, StreamMap, ConnectionTransporter, StreamTransporter};
use futures::*;
#[derive(Debug)]
pub struct FlowControl<T> {
inner: T,
initial_local_window_size: u32,
initial_remote_window_size: u32,
}
impl<T, U> FlowControl<T>
@@ -14,8 +16,85 @@ impl<T, U> FlowControl<T>
T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
T: StreamTransporter
{
pub fn new(inner: T) -> FlowControl<T> {
FlowControl { inner }
pub fn new(initial_local_window_size: u32,
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.
pub fn increment_window_size(&mut self, sz: WindowSize) {
pub fn grow_window(&mut self, sz: WindowSize) {
if sz <= self.underflow {
self.underflow -= sz;
return;

View File

@@ -1,7 +1,7 @@
use {hpack, ConnectionError};
use frame::{self, Frame, Kind};
use frame::DEFAULT_SETTINGS_HEADER_TABLE_SIZE;
use proto::ReadySink;
use proto::{ConnectionTransporter, ReadySink};
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>
where T: AsyncRead,
{

View File

@@ -1,6 +1,6 @@
use {hpack, ConnectionError, FrameSize};
use frame::{self, Frame};
use proto::ReadySink;
use proto::{ConnectionTransporter, ReadySink};
use futures::*;
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>
where T: AsyncWrite,
B: Buf,

View File

@@ -20,7 +20,7 @@ pub use self::settings::Settings;
pub use self::stream_tracker::StreamTracker;
use self::state::StreamState;
use {frame, Peer, StreamId};
use {frame, ConnectionError, Peer, StreamId};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_io::codec::length_delimited;
@@ -31,7 +31,20 @@ use ordermap::OrderMap;
use fnv::FnvHasher;
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> =
Settings<
FlowControl<
@@ -44,15 +57,46 @@ type Framer<T, B> =
FramedRead<
FramedWrite<T, B>>;
pub type WindowSize = u32;
#[derive(Debug)]
struct StreamMap {
#[derive(Debug, Default)]
pub struct StreamMap {
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_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
// weird, but oh well...
//
// We first create a Settings directly around a framed writer
let settings = Settings::new(
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
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>
where T: AsyncRead + AsyncWrite,
P: Peer,
B: IntoBuf,
{
let settings = transport.swap_inner(|io| {
// Delimit the frames
let framed_read = length_delimited::Builder::new()
let initial_local_window_size = settings.local_settings().initial_window_size();
let initial_remote_window_size = settings.remote_settings().initial_window_size();
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()
.length_field_length(3)
.length_adjustment(9)
.num_skip(0) // Don't skip the header
.new_read(io);
// Map to `Frame` types
let framed = FramedRead::new(framed_read);
FlowControl::new(
StreamTracker::new(
FlowControl::new(initial_local_window_size, initial_remote_window_size,
StreamTracker::new(local_max_concurrency, remote_max_concurrency,
PingPong::new(
framed)))
FramedRead::new(framer))))
});
// Finally, return the constructed `Connection`
connection::new(settings)
connection::new(transport)
}

View File

@@ -1,7 +1,7 @@
use ConnectionError;
use frame::{Frame, Ping};
use frame::{Frame, Ping, SettingSet};
use futures::*;
use proto::ReadySink;
use proto::{ConnectionTransporter, ReadySink};
/// Acknowledges ping requests from the remote.
#[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>
where T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
{

View File

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

View File

@@ -47,8 +47,9 @@ use proto::FlowController;
#[derive(Debug, Copy, Clone)]
pub enum StreamState {
Idle,
ReservedLocal,
ReservedRemote,
// TODO: these states shouldn't count against concurrency limits:
//ReservedLocal,
//ReservedRemote,
Open {
local: PeerState,
remote: PeerState,
@@ -65,7 +66,7 @@ impl StreamState {
/// caller should send the the returned window size increment to the remote.
///
/// 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::PeerState::*;
@@ -75,11 +76,27 @@ impl StreamState {
match self {
&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
/// data.
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
/// 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::PeerState::*;
@@ -108,7 +125,22 @@ impl StreamState {
match self {
&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.
///
/// Returns true if this state transition results in iniitializing the
@@ -212,8 +204,6 @@ impl StreamState {
Closed | HalfClosedRemote(..) => {
Err(ProtocolError.into())
}
_ => unimplemented!(),
}
}
@@ -301,8 +291,6 @@ impl StreamState {
Closed | HalfClosedLocal(..) => {
Err(UnexpectedFrameType.into())
}
_ => unimplemented!(),
}
}

View File

@@ -1,30 +1,72 @@
use ConnectionError;
use frame::{self, Frame};
use proto::{ReadySink, StreamMap, StreamTransporter, WindowSize};
use proto::{ReadySink, StreamMap, ConnectionTransporter, StreamTransporter};
use futures::*;
#[derive(Debug)]
pub struct StreamTracker<T> {
inner: T,
streams: StreamMap,
local_max_concurrency: Option<u32>,
remote_max_concurrency: Option<u32>,
}
impl<T, U> StreamTracker<T>
where T: Stream<Item = Frame, Error = ConnectionError>,
T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>
{
pub fn new(inner: T) -> StreamTracker<T> {
StreamTracker { inner }
pub fn new(local_max_concurrency: Option<u32>,
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> {
fn streams(&self) -> &StreamMap {
unimplemented!()
&self.streams
}
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)
}
}