wip: Sketch out stream state refactor

introduce the StreamTransporter trait, which exposes a map containing
all active stream states.

add skeletons for StreamTracker and FlowControl.  StreamTracker drives
all state changes
This commit is contained in:
Oliver Gould
2017-07-15 18:39:45 +00:00
parent d0c55c52e9
commit 1ed4b7e56a
8 changed files with 270 additions and 141 deletions

View File

@@ -2,7 +2,7 @@ use {Frame, FrameSize};
use client::Client;
use error::{self, ConnectionError};
use frame::{self, StreamId};
use proto::{self, Peer, ReadySink, State, FlowController, WindowSize};
use proto::{self, Peer, ReadySink, StreamState, FlowController, WindowSize};
use server::Server;
use tokio_io::{AsyncRead, AsyncWrite};
@@ -14,15 +14,14 @@ use futures::*;
use ordermap::OrderMap;
use fnv::FnvHasher;
use std::hash::BuildHasherDefault;
use std::marker::PhantomData;
/// An H2 connection
#[derive(Debug)]
pub struct Connection<T, P, B: IntoBuf = Bytes> {
inner: proto::Inner<T, B::Buf>,
streams: StreamMap<State>,
inner: proto::Transport<T, B::Buf>,
streams: StreamMap<StreamState>,
peer: PhantomData<P>,
/// Tracks the connection-level flow control window for receiving data from the
@@ -41,7 +40,7 @@ pub struct Connection<T, P, B: IntoBuf = Bytes> {
type StreamMap<T> = OrderMap<StreamId, T, BuildHasherDefault<FnvHasher>>;
pub fn new<T, P, B>(transport: proto::Inner<T, B::Buf>)
pub fn new<T, P, B>(transport: proto::Transport<T, B::Buf>)
-> Connection<T, P, B>
where T: AsyncRead + AsyncWrite,
P: Peer,
@@ -256,7 +255,7 @@ impl<T, P, B> Stream for Connection<T, P, B>
let init_window_size = self.inner.local_settings().initial_window_size();
let stream_initialized = try!(self.streams.entry(stream_id)
.or_insert(State::default())
.or_insert(StreamState::default())
.recv_headers::<P>(end_of_stream, init_window_size));
if stream_initialized {
@@ -347,7 +346,7 @@ impl<T, P, B> Sink for Connection<T, P, B>
// ACTUALLY(ver), maybe not?
// https://github.com/http2/http2-spec/commit/c83c8d911e6b6226269877e446a5cad8db921784
let stream_initialized = try!(self.streams.entry(id)
.or_insert(State::default())
.or_insert(StreamState::default())
.send_headers::<P>(end_of_stream, init_window_size));
if stream_initialized {

View File

@@ -1,77 +1,70 @@
use proto::WindowSize;
use ConnectionError;
use frame::{self, Frame};
use proto::{ReadySink, StreamMap, StreamTransporter, WindowSize};
#[derive(Clone, Copy, Debug)]
pub struct WindowUnderflow;
use futures::*;
pub const DEFAULT_INITIAL_WINDOW_SIZE: WindowSize = 65_535;
#[derive(Copy, Clone, Debug)]
pub struct FlowController {
/// Amount that may be claimed.
window_size: WindowSize,
/// Amount to be removed by future increments.
underflow: WindowSize,
/// The amount that has been incremented but not yet advertised (to the application or
/// the remote).
next_window_update: WindowSize,
#[derive(Debug)]
pub struct FlowControl<T> {
inner: T,
}
impl Default for FlowController {
fn default() -> Self {
Self::new(DEFAULT_INITIAL_WINDOW_SIZE)
impl<T, U> FlowControl<T>
where T: Stream<Item = Frame, Error = ConnectionError>,
T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
T: StreamTransporter
{
pub fn new(inner: T) -> FlowControl<T> {
FlowControl { inner }
}
}
impl FlowController {
pub fn new(window_size: WindowSize) -> FlowController {
FlowController {
window_size,
underflow: 0,
next_window_update: 0,
impl<T: StreamTransporter> StreamTransporter for FlowControl<T> {
fn streams(&self) -> &StreamMap {
self.inner.streams()
}
fn streams_mut(&mut self) -> &mut StreamMap {
self.inner.streams_mut()
}
}
/// Reduce future capacity of the window.
///
/// This accomodates updates to SETTINGS_INITIAL_WINDOW_SIZE.
pub fn shrink_window(&mut self, decr: WindowSize) {
self.underflow += decr;
impl<T> Stream for FlowControl<T>
where T: Stream<Item = Frame, Error = ConnectionError>,
T: StreamTransporter,
{
type Item = T::Item;
type Error = T::Error;
fn poll(&mut self) -> Poll<Option<T::Item>, T::Error> {
self.inner.poll()
}
}
/// Claims the provided amount from the window, if there is enough space.
///
/// Fails when `take_window_update()` hasn't returned at least `sz` more bytes than
/// have been previously claimed.
pub fn claim_window(&mut self, sz: WindowSize) -> Result<(), WindowUnderflow> {
if self.window_size < sz {
return Err(WindowUnderflow);
impl<T, U> Sink for FlowControl<T>
where T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
T: StreamTransporter,
{
type SinkItem = T::SinkItem;
type SinkError = T::SinkError;
fn start_send(&mut self, item: Frame<U>) -> StartSend<T::SinkItem, T::SinkError> {
self.inner.start_send(item)
}
self.window_size -= sz;
Ok(())
fn poll_complete(&mut self) -> Poll<(), T::SinkError> {
self.inner.poll_complete()
}
}
/// Applies a window increment immediately.
pub fn increment_window_size(&mut self, sz: WindowSize) {
if sz <= self.underflow {
self.underflow -= sz;
return;
}
let added = sz - self.underflow;
self.window_size += added;
self.next_window_update += added;
self.underflow = 0;
}
/// Obtains and clears an unadvertised window update.
pub fn take_window_update(&mut self) -> Option<WindowSize> {
if self.next_window_update == 0 {
return None;
}
let incr = self.next_window_update;
self.next_window_update = 0;
Some(incr)
impl<T, U> ReadySink for FlowControl<T>
where T: Stream<Item = Frame, Error = ConnectionError>,
T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
T: ReadySink,
T: StreamTransporter,
{
fn poll_ready(&mut self) -> Poll<(), ConnectionError> {
self.inner.poll_ready()
}
}

View File

@@ -0,0 +1,83 @@
use proto::WindowSize;
#[derive(Clone, Copy, Debug)]
pub struct WindowUnderflow;
pub const DEFAULT_INITIAL_WINDOW_SIZE: WindowSize = 65_535;
#[derive(Copy, Clone, Debug)]
pub struct FlowController {
/// Amount that may be claimed.
window_size: WindowSize,
/// Amount to be removed by future increments.
underflow: WindowSize,
/// The amount that has been incremented but not yet advertised (to the application or
/// the remote).
next_window_update: WindowSize,
}
impl Default for FlowController {
fn default() -> Self {
Self::new(DEFAULT_INITIAL_WINDOW_SIZE)
}
}
impl FlowController {
pub fn new(window_size: WindowSize) -> FlowController {
FlowController {
window_size,
underflow: 0,
next_window_update: 0,
}
}
/// Reduce future capacity of the window.
///
/// This accomodates updates to SETTINGS_INITIAL_WINDOW_SIZE.
pub fn shrink_window(&mut self, decr: WindowSize) {
self.underflow += decr;
}
/// Claims the provided amount from the window, if there is enough space.
///
/// Fails when `take_window_update()` hasn't returned at least `sz` more bytes than
/// have been previously claimed.
pub fn claim_window(&mut self, sz: WindowSize) -> Result<(), WindowUnderflow> {
if self.window_size < sz {
return Err(WindowUnderflow);
}
self.window_size -= sz;
Ok(())
}
/// Applies a window increment immediately.
pub fn increment_window_size(&mut self, sz: WindowSize) {
if sz <= self.underflow {
self.underflow -= sz;
return;
}
let added = sz - self.underflow;
self.window_size += added;
self.next_window_update += added;
self.underflow = 0;
}
/// Obtains and clears an unadvertised window update.
pub fn take_window_update(&mut self) -> Option<WindowSize> {
if self.next_window_update == 0 {
return None;
}
let incr = self.next_window_update;
self.next_window_update = 0;
Some(incr)
}
}
#[test]
fn test() {
let mut fc = FlowController::new(65_535);
}

View File

@@ -1,43 +1,62 @@
mod connection;
mod flow_control;
mod flow_controller;
mod framed_read;
mod framed_write;
mod ping_pong;
mod ready;
mod settings;
mod state;
mod window_update;
mod stream_tracker;
pub use self::connection::Connection;
pub use self::flow_control::FlowController;
pub use self::flow_control::FlowControl;
pub use self::flow_controller::FlowController;
pub use self::framed_read::FramedRead;
pub use self::framed_write::FramedWrite;
pub use self::ping_pong::PingPong;
pub use self::ready::ReadySink;
pub use self::settings::Settings;
pub use self::state::{PeerState, State};
pub use self::window_update::WindowUpdate;
pub use self::stream_tracker::StreamTracker;
use self::state::StreamState;
use {frame, Peer};
use {frame, Peer, StreamId};
use tokio_io::{AsyncRead, AsyncWrite};
use tokio_io::codec::length_delimited;
use bytes::{Buf, IntoBuf};
type Inner<T, B> =
Settings<
PingPong<
Framed<T, B>,
B>>;
use ordermap::OrderMap;
use fnv::FnvHasher;
use std::hash::BuildHasherDefault;
type Framed<T, B> =
/// Represents
type Transport<T, B> =
Settings<
FlowControl<
StreamTracker<
PingPong<
Framer<T, B>,
B>>>>;
type Framer<T, B> =
FramedRead<
FramedWrite<T, B>>;
pub type WindowSize = u32;
#[derive(Debug)]
struct StreamMap {
inner: OrderMap<StreamId, StreamState, BuildHasherDefault<FnvHasher>>
}
trait StreamTransporter {
fn streams(&self)-> &StreamMap;
fn streams_mut(&mut self) -> &mut StreamMap;
}
/// Create a full H2 transport from an I/O handle.
///
/// This is called as the final step of the client handshake future.
@@ -91,8 +110,10 @@ pub fn from_server_handshaker<T, P, B>(transport: Settings<FramedWrite<T, B::Buf
// Map to `Frame` types
let framed = FramedRead::new(framed_read);
// Add ping/pong responder.
PingPong::new(framed)
FlowControl::new(
StreamTracker::new(
PingPong::new(
framed)))
});
// Finally, return the constructed `Connection`

View File

@@ -20,7 +20,11 @@ impl<T, U> PingPong<T, U>
pong: None,
}
}
}
impl<T, U> PingPong<T, U>
where T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
{
fn try_send_pong(&mut self) -> Poll<(), ConnectionError> {
if let Some(pong) = self.pong.take() {
if let AsyncSink::NotReady(pong) = self.inner.start_send(pong)? {
@@ -77,8 +81,7 @@ impl<T, U> Stream for PingPong<T, U>
}
impl<T, U> Sink for PingPong<T, U>
where T: Stream<Item = Frame, Error = ConnectionError>,
T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
where T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
{
type SinkItem = Frame<U>;
type SinkError = ConnectionError;
@@ -103,8 +106,7 @@ impl<T, U> Sink for PingPong<T, U>
}
impl<T, U> ReadySink for PingPong<T, U>
where T: Stream<Item = Frame, Error = ConnectionError>,
T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
where T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
T: ReadySink,
{
fn poll_ready(&mut self) -> Poll<(), ConnectionError> {

View File

@@ -45,7 +45,7 @@ use proto::FlowController;
/// R: RST_STREAM frame
/// ```
#[derive(Debug, Copy, Clone)]
pub enum State {
pub enum StreamState {
Idle,
ReservedLocal,
ReservedRemote,
@@ -58,7 +58,7 @@ pub enum State {
Closed,
}
impl State {
impl StreamState {
/// Updates the local flow controller so that the remote may send `incr` more bytes.
///
/// Returns the amount of capacity created, accounting for window size changes. The
@@ -66,7 +66,7 @@ impl State {
///
/// If the remote is closed, None is returned.
pub fn increment_send_window_size(&mut self, incr: u32) {
use self::State::*;
use self::StreamState::*;
use self::PeerState::*;
if incr == 0 {
@@ -83,7 +83,7 @@ impl State {
/// Consumes newly-advertised capacity to inform the local endpoint it may send more
/// data.
pub fn take_send_window_update(&mut self) -> Option<u32> {
use self::State::*;
use self::StreamState::*;
use self::PeerState::*;
match self {
@@ -99,7 +99,7 @@ impl State {
/// 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) {
use self::State::*;
use self::StreamState::*;
use self::PeerState::*;
if incr == 0 {
@@ -116,7 +116,7 @@ impl State {
/// Consumes newly-advertised capacity to inform the local endpoint it may send more
/// data.
pub fn take_recv_window_update(&mut self) -> Option<u32> {
use self::State::*;
use self::StreamState::*;
use self::PeerState::*;
match self {
@@ -143,7 +143,7 @@ impl State {
/// > 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::State::*;
use self::StreamState::*;
use self::PeerState::*;
match self {
@@ -161,7 +161,7 @@ impl State {
/// 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::State::*;
//use self::StreamState::*;
//use self::PeerState::*;
unimplemented!()
}
@@ -175,7 +175,7 @@ impl State {
initial_recv_window_size: u32)
-> Result<bool, ConnectionError>
{
use self::State::*;
use self::StreamState::*;
use self::PeerState::*;
match *self {
@@ -218,7 +218,7 @@ impl State {
}
pub fn recv_data(&mut self, eos: bool, len: FrameSize) -> Result<(), ConnectionError> {
use self::State::*;
use self::StreamState::*;
match *self {
Open { local, mut remote } => {
@@ -256,7 +256,7 @@ impl State {
initial_window_size: u32)
-> Result<bool, ConnectionError>
{
use self::State::*;
use self::StreamState::*;
use self::PeerState::*;
match *self {
@@ -307,7 +307,7 @@ impl State {
}
pub fn send_data(&mut self, eos: bool, len: FrameSize) -> Result<(), ConnectionError> {
use self::State::*;
use self::StreamState::*;
match *self {
Open { mut local, remote } => {
@@ -337,9 +337,9 @@ impl State {
}
}
impl Default for State {
fn default() -> State {
State::Idle
impl Default for StreamState {
fn default() -> StreamState {
StreamState::Idle
}
}

View File

@@ -0,0 +1,67 @@
use ConnectionError;
use frame::{self, Frame};
use proto::{ReadySink, StreamMap, StreamTransporter, WindowSize};
use futures::*;
#[derive(Debug)]
pub struct StreamTracker<T> {
inner: T,
}
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 }
}
}
impl<T> StreamTransporter for StreamTracker<T> {
fn streams(&self) -> &StreamMap {
unimplemented!()
}
fn streams_mut(&mut self) -> &mut StreamMap {
unimplemented!()
}
}
impl<T, U> Stream for StreamTracker<T>
where T: Stream<Item = Frame<U>, Error = ConnectionError>,
{
type Item = T::Item;
type Error = T::Error;
fn poll(&mut self) -> Poll<Option<T::Item>, T::Error> {
self.inner.poll()
}
}
impl<T, U> Sink for StreamTracker<T>
where T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
{
type SinkItem = T::SinkItem;
type SinkError = T::SinkError;
fn start_send(&mut self, item: T::SinkItem) -> StartSend<T::SinkItem, T::SinkError> {
self.inner.start_send(item)
}
fn poll_complete(&mut self) -> Poll<(), T::SinkError> {
self.inner.poll_complete()
}
}
impl<T, U> ReadySink for StreamTracker<T>
where T: Stream<Item = Frame, Error = ConnectionError>,
T: Sink<SinkItem = Frame<U>, SinkError = ConnectionError>,
T: ReadySink,
{
fn poll_ready(&mut self) -> Poll<(), ConnectionError> {
self.inner.poll_ready()
}
}

View File

@@ -1,36 +0,0 @@
use StreamId;
use frame;
#[derive(Debug)]
pub enum WindowUpdate {
Connection { increment: u32 },
Stream { id: StreamId, increment: u32 },
}
impl WindowUpdate {
pub fn increment(&self) -> u32 {
match *self {
WindowUpdate::Connection { increment } |
WindowUpdate::Stream { increment, .. } => increment
}
}
}
impl From<WindowUpdate> for frame::WindowUpdate {
fn from(src: WindowUpdate) -> Self {
match src {
WindowUpdate::Connection { increment } => {
frame::WindowUpdate::new(StreamId::zero(), increment)
}
WindowUpdate::Stream { id, increment } => {
frame::WindowUpdate::new(id, increment)
}
}
}
}
impl From<WindowUpdate> for frame::Frame {
fn from(src: WindowUpdate) -> Self {
frame::Frame::WindowUpdate(src.into())
}
}