Avoid prematurely unlinking streams in send_reset, in some cases. (#319)

Because `send_reset` called `recv_err`, which calls `reclaim_all_capacity`,
which eventually calls `transition(stream, ..)` -- all of which happens _before_
the RESET frame is enqueued -- it was possible for the stream to get unlinked
from the store (if there was any connection-level capacity to reassign). This
could then cause the stream to get "leaked" on drop/EOF since it would no longer
be iterated.

Fix this by delaying the call to `reclaim_all_capacity` _after_ enqueueing the
RESET frame.

A test demonstrating the issue is included.
This commit is contained in:
Geoffry Song
2018-10-16 11:59:22 -07:00
committed by Carl Lerche
parent 9bbbe7ebd5
commit ea8b8ac2fd
2 changed files with 79 additions and 1 deletions

View File

@@ -1061,6 +1061,79 @@ fn drop_pending_open() {
client.join(srv).wait().unwrap();
}
#[test]
fn malformed_response_headers_dont_unlink_stream() {
// This test checks that receiving malformed headers frame on a stream with
// no remaining references correctly resets the stream, without prematurely
// unlinking it.
let _ = ::env_logger::try_init();
let (io, srv) = mock::new();
let (drop_tx, drop_rx) = futures::sync::oneshot::channel();
let (queued_tx, queued_rx) = futures::sync::oneshot::channel();
let srv = srv
.assert_client_handshake()
.unwrap()
.recv_settings()
.recv_frame(frames::headers(1).request("GET", "http://example.com/"))
.recv_frame(frames::headers(3).request("GET", "http://example.com/"))
.recv_frame(frames::headers(5).request("GET", "http://example.com/"))
.map(move |h| {
drop_tx.send(()).unwrap();
h
})
.wait_for(queued_rx)
.send_bytes(&[
// 2 byte frame
0, 0, 2,
// type: HEADERS
1,
// flags: END_STREAM | END_HEADERS
5,
// stream identifier: 3
0, 0, 0, 3,
// data - invalid (pseudo not at end of block)
144, 135
// Per the spec, this frame should cause a stream error of type
// PROTOCOL_ERROR.
])
.close()
;
fn request() -> Request<()> {
Request::builder()
.uri("http://example.com/")
.body(())
.unwrap()
}
let client = client::Builder::new()
.handshake::<_, Bytes>(io)
.expect("handshake")
.and_then(move |(mut client, conn)| {
let (_req1, mut send1) = client.send_request(
request(), false).unwrap();
// Use up most of the connection window.
send1.send_data(vec![0; 65534].into(), true).unwrap();
let (req2, mut send2) = client.send_request(
request(), false).unwrap();
let (req3, mut send3) = client.send_request(
request(), false).unwrap();
conn.expect("h2").join(drop_rx.then(move |_| {
// Use up the remainder of the connection window.
send2.send_data(vec![0; 2].into(), true).unwrap();
// Queue up for more connection window.
send3.send_data(vec![0; 1].into(), true).unwrap();
queued_tx.send(()).unwrap();
Ok((req2, req3))
}))
});
client.join(srv).wait().unwrap();
}
const SETTINGS: &'static [u8] = &[0, 0, 0, 4, 0, 0, 0, 0, 0];
const SETTINGS_ACK: &'static [u8] = &[0, 0, 0, 4, 1, 0, 0, 0, 0];