Reset any queued stream on receipt of remote reset (#258)
Fixes #256. This PR changes `state::recv_reset` so any closed stream with queued send is immediately reset (and thus, the queue is cleared) on receipt of a `RST_STREAM` frame from the remote. This fixes the panic encountered by the test @goffrie posted in #256, where the stream is scheduled to close, receives a `RST_STREAM` frame, and sets the buffered capacity to 0, but isn't removed from the send queue, so we hit an assertion (or wrap, if debug assertions are disabled) when subtracting a sent frame's size from the buffered size. Signed-off-by: Eliza Weisman <eliza@buoyant.io>
This commit is contained in:
@@ -593,6 +593,8 @@ impl Prioritize {
|
||||
// To be safe, we just always ask the stream.
|
||||
let is_counted = stream.is_counted();
|
||||
let is_pending_reset = stream.is_pending_reset_expiration();
|
||||
trace!(" --> stream={:?}; is_pending_reset={:?};",
|
||||
stream.id, is_pending_reset);
|
||||
|
||||
let frame = match stream.pending_send.pop_front(buffer) {
|
||||
Some(Frame::Data(mut frame)) => {
|
||||
@@ -603,13 +605,14 @@ impl Prioritize {
|
||||
|
||||
trace!(
|
||||
" --> data frame; stream={:?}; sz={}; eos={:?}; window={}; \
|
||||
available={}; requested={}",
|
||||
available={}; requested={}; buffered={};",
|
||||
frame.stream_id(),
|
||||
sz,
|
||||
frame.is_end_stream(),
|
||||
stream_capacity,
|
||||
stream.send_flow.available(),
|
||||
stream.requested_send_capacity
|
||||
stream.requested_send_capacity,
|
||||
stream.buffered_send_data,
|
||||
);
|
||||
|
||||
// Zero length data frames always have capacity to
|
||||
|
||||
@@ -214,20 +214,37 @@ impl State {
|
||||
}
|
||||
|
||||
/// The remote explicitly sent a RST_STREAM.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `reason`: the reason field of the received RST_STREAM frame.
|
||||
/// - `queued`: true if this stream has frames in the pending send queue.
|
||||
pub fn recv_reset(&mut self, reason: Reason, queued: bool) {
|
||||
|
||||
match self.inner {
|
||||
Closed(Cause::EndStream) if queued => {
|
||||
// If the stream has a queued EOS frame, transition to peer
|
||||
// reset.
|
||||
trace!("recv_reset: reason={:?}; queued=true", reason);
|
||||
self.inner = Closed(Cause::Proto(reason));
|
||||
},
|
||||
Closed(..) => {},
|
||||
_ => {
|
||||
trace!("recv_reset; reason={:?}", reason);
|
||||
// If the stream is already in a `Closed` state, do nothing,
|
||||
// provided that there are no frames still in the send queue.
|
||||
Closed(..) if !queued => {},
|
||||
// A notionally `Closed` stream may still have queued frames in
|
||||
// the following cases:
|
||||
//
|
||||
// - if the cause is `Cause::Scheduled(..)` (i.e. we have not
|
||||
// actually closed the stream yet).
|
||||
// - if the cause is `Cause::EndStream`: we transition to this
|
||||
// state when an EOS frame is *enqueued* (so that it's invalid
|
||||
// to enqueue more frames), not when the EOS frame is *sent*;
|
||||
// therefore, there may still be frames ahead of the EOS frame
|
||||
// in the send queue.
|
||||
//
|
||||
// In either of these cases, we want to overwrite the stream's
|
||||
// previous state with the received RST_STREAM, so that the queue
|
||||
// will be cleared by `Prioritize::pop_frame`.
|
||||
state => {
|
||||
trace!(
|
||||
"recv_reset; reason={:?}; state={:?}; queued={:?}",
|
||||
reason, state, queued
|
||||
);
|
||||
self.inner = Closed(Cause::Proto(reason));
|
||||
},
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user