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