Refactor errors (#46)

This patch does a bunch of refactoring, mostly around error types, but it also
paves the way to allow `Codec` to be used standalone.

* `Codec` (and `FramedRead` / `FramedWrite`) is broken out into a codec module.
* An h2-codec crate is created that re-exports the frame and codec modules.
* New error types are introduced in the internals:
  * `RecvError` represents errors caused by trying to receive a frame.
  * `SendError` represents errors caused by trying to send a frame.
  * `UserError` is an enum of potential errors caused by invalid usage
    by the user of the lib.
  * `ProtoError` is either a `Reason` or an `io::Error`. However it doesn't
    specify connection or stream level.
  * `h2::Error` is an opaque error type and is the only error type exposed
    by the public API (used to be `ConnectionError`).

There are misc code changes to enable this as well. The biggest is a new "sink"
API for `Codec`. It provides buffer which queues up a frame followed by flush
which writes everything that is queued. This departs from the `Sink` trait in
order to provide more accurate error values. For example, buffer can never fail
(but it will panic if `poll_ready` is not called first).
This commit is contained in:
Carl Lerche
2017-09-02 11:12:50 -07:00
committed by GitHub
parent 6fd9674759
commit c122e97127
37 changed files with 1043 additions and 1027 deletions

View File

@@ -1,9 +1,11 @@
use {client, frame, server, ConnectionError};
use {client, frame, server, proto};
use frame::Reason;
use codec::{SendError, RecvError};
use proto::*;
use http::Request;
use futures::{Sink, Stream};
use futures::{Stream};
use bytes::{Bytes, IntoBuf};
use tokio_io::{AsyncRead, AsyncWrite};
@@ -73,7 +75,10 @@ impl<T, P, B> Connection<T, P, B>
}
/// Returns `Ready` when the connection is ready to receive a frame.
fn poll_ready(&mut self) -> Poll<(), ConnectionError> {
///
/// Returns `RecvError` as this may raise errors that are caused by delayed
/// processing of received frames.
fn poll_ready(&mut self) -> Poll<(), RecvError> {
// The order of these calls don't really matter too much as only one
// should have pending work.
try_ready!(self.ping_pong.send_pending_pong(&mut self.codec));
@@ -83,84 +88,97 @@ impl<T, P, B> Connection<T, P, B>
Ok(().into())
}
/// Returns `Ready` when new the connection is able to support a new request stream.
pub fn poll_send_request_ready(&mut self) -> Poll<(), ConnectionError> {
self.streams.poll_send_request_ready()
}
/// Advances the internal state of the connection.
pub fn poll(&mut self) -> Poll<(), ConnectionError> {
use error::ConnectionError::*;
pub fn poll(&mut self) -> Poll<(), proto::Error> {
use codec::RecvError::*;
loop {
// TODO: probably clean up this glob of code
match self.state {
// When open, continue to poll a frame
State::Open => {},
// In an error state
_ => {
try_ready!(self.poll_complete());
State::Open => {
match self.poll2() {
// The connection has shutdown normally
Ok(Async::Ready(())) => return Ok(().into()),
// The connection is not ready to make progress
Ok(Async::NotReady) => {
// Ensure all window updates have been sent.
//
// This will also handle flushing `self.codec`
try_ready!(self.streams.poll_complete(&mut self.codec));
// GO_AWAY frame has been sent, return the error
return Err(self.state.error().unwrap().into());
}
}
return Ok(Async::NotReady);
}
// Attempting to read a frame resulted in a connection level
// error. This is handled by setting a GO_AWAY frame followed by
// terminating the connection.
Err(Connection(e)) => {
debug!("Connection::poll; err={:?}", e);
match self.poll2() {
Err(Proto(e)) => {
debug!("Connection::poll; err={:?}", e);
let last_processed_id = self.streams.recv_err(&e.into());
let frame = frame::GoAway::new(last_processed_id, e);
// Reset all active streams
let last_processed_id = self.streams.recv_err(&e.into());
self.state = State::GoAway(frame);
// Create the GO_AWAY frame with the last_processed_id
let frame = frame::GoAway::new(last_processed_id, e);
// Transition to the going away state.
self.state = State::GoAway(frame);
}
// Attempting to read a frame resulted in a stream level error.
// This is handled by resetting the frame then trying to read
// another frame.
Err(Stream { id, reason }) => {
trace!("stream level error; id={:?}; reason={:?}", id, reason);
self.streams.send_reset(id, reason);
}
// Attempting to read a frame resulted in an I/O error. All
// active streams must be reset.
//
// TODO: Are I/O errors recoverable?
Err(Io(e)) => {
let e = e.into();
// Reset all active streams
self.streams.recv_err(&e);
// Return the error
return Err(e);
}
}
},
State::GoAway(frame) => {
// Ensure the codec is ready to accept the frame
try_ready!(self.codec.poll_ready());
// Buffer the GO_AWAY frame
self.codec.buffer(frame.into())
.ok().expect("invalid GO_AWAY frame");
// GO_AWAY sent, transition the connection to an errored state
self.state = State::Flush(frame.reason());
}
Err(e) => {
// TODO: Are I/O errors recoverable?
self.streams.recv_err(&e);
return Err(e);
State::Flush(reason) => {
// Flush the codec
try_ready!(self.codec.flush());
// Transition the state to error
self.state = State::Error(reason);
}
State::Error(reason) => {
return Err(reason.into());
}
ret => return ret,
}
}
}
fn poll2(&mut self) -> Poll<(), ConnectionError> {
fn poll2(&mut self) -> Poll<(), RecvError> {
use frame::Frame::*;
use proto::ProtoError::*;
loop {
// First, ensure that the `Connection` is able to receive a frame
try_ready!(self.poll_ready());
trace!("polling codec");
let frame = match self.codec.poll() {
// Receive a frame
Ok(Async::Ready(frame)) => frame,
// Socket not ready, try to flush any pending data
Ok(Async::NotReady) => {
// Flush any pending writes
let _ = try!(self.poll_complete());
return Ok(Async::NotReady);
}
// Connection level error, set GO_AWAY and close connection
Err(Connection(reason)) => {
return Err(ConnectionError::Proto(reason));
}
// Stream level error, reset the stream
Err(Stream { id, reason }) => {
trace!("stream level error; id={:?}; reason={:?}", id, reason);
self.streams.send_reset(id, reason);
continue;
}
// I/O error, nothing more can be done
Err(Io(err)) => {
return Err(err.into());
}
};
debug!("recv; frame={:?}", frame);
match frame {
match try_ready!(self.codec.poll()) {
Some(Headers(frame)) => {
trace!("recv HEADERS; frame={:?}", frame);
try!(self.streams.recv_headers(frame));
@@ -184,7 +202,7 @@ impl<T, P, B> Connection<T, P, B>
Some(GoAway(_)) => {
// TODO: handle the last_processed_id. Also, should this be
// handled as an error?
// let _ = ConnectionError::Proto(frame.reason());
// let _ = RecvError::Proto(frame.reason());
return Ok(().into());
}
Some(Ping(frame)) => {
@@ -207,46 +225,20 @@ impl<T, P, B> Connection<T, P, B>
}
}
}
fn poll_complete(&mut self) -> Poll<(), ConnectionError> {
loop {
match self.state {
State::Open => {
try_ready!(self.poll_ready());
// Ensure all window updates have been sent.
try_ready!(self.streams.poll_complete(&mut self.codec));
return Ok(().into());
}
State::GoAway(frame) => {
if !self.codec.start_send(frame.into())?.is_ready() {
// Not ready to send the frame... try again later.
return Ok(Async::NotReady);
}
// GO_AWAY sent, transition the connection to an errored state
self.state = State::Flush(frame.reason());
}
State::Flush(reason) => {
try_ready!(self.codec.poll_complete());
self.state = State::Error(reason);
}
State::Error(..) => {
return Ok(().into());
}
}
}
}
}
impl<T, B> Connection<T, client::Peer, B>
where T: AsyncRead + AsyncWrite,
B: IntoBuf,
{
/// Returns `Ready` when new the connection is able to support a new request stream.
pub fn poll_send_request_ready(&mut self) -> Async<()> {
self.streams.poll_send_request_ready()
}
/// Initialize a new HTTP/2.0 stream and send the message.
pub fn send_request(&mut self, request: Request<()>, end_of_stream: bool)
-> Result<StreamRef<B::Buf, client::Peer>, ConnectionError>
-> Result<StreamRef<B::Buf, client::Peer>, SendError>
{
self.streams.send_request(request, end_of_stream)
}
@@ -260,14 +252,3 @@ impl<T, B> Connection<T, server::Peer, B>
self.streams.next_incoming()
}
}
// ====== impl State =====
impl State {
fn error(&self) -> Option<Reason> {
match *self {
State::Error(reason) => Some(reason),
_ => None,
}
}
}