Skip to content

Commit eb61740

Browse files
committed
Clean up streams in h3::Connection when done
Currently, while `quiche::Connection` will clean up streams from its `StreamMap`, `quiche::h3::Connection` does not. As a result, long lived connections will slowly leak memory through the stream map in the connection. This patch forwards the `collect()` event from the `quiche::Connection` to the `quiche::h3::Connection` through the `h3::Connection::poll()` call, allowing the `h3::Connection` to clean up its unused streams.
1 parent 5ea8d8e commit eb61740

File tree

2 files changed

+87
-0
lines changed

2 files changed

+87
-0
lines changed

quiche/src/h3/mod.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2070,6 +2070,10 @@ impl Connection {
20702070
return Ok((finished, Event::Finished));
20712071
}
20722072

2073+
for stream_id in conn.streams.collected_streams() {
2074+
self.streams.remove(&stream_id);
2075+
}
2076+
20732077
Err(Error::Done)
20742078
}
20752079

@@ -3211,6 +3215,13 @@ impl Connection {
32113215
.decoder_stream_bytes,
32123216
}
32133217
}
3218+
3219+
#[cfg(test)]
3220+
/// Indicates how many streams are currently being tracked for use
3221+
/// in resource tests.
3222+
pub fn stream_count(&self) -> usize {
3223+
self.streams.len()
3224+
}
32143225
}
32153226

32163227
/// Generates an HTTP/3 GREASE variable length integer.
@@ -7349,6 +7360,68 @@ mod tests {
73497360
assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));
73507361
assert_eq!(s.poll_client(), Err(Error::Done));
73517362
}
7363+
7364+
#[test]
7365+
fn h3_cleanup_streams() {
7366+
let mut buf = [0; 65535];
7367+
let mut s = Session::new().unwrap();
7368+
s.handshake().unwrap();
7369+
7370+
let init_streams_client = s.client.stream_count();
7371+
let init_streams_server = s.client.stream_count();
7372+
7373+
// Client sends HEADERS and doesn't fin
7374+
let (stream, req) = s.send_request(false).unwrap();
7375+
7376+
let ev_headers = Event::Headers {
7377+
list: req,
7378+
more_frames: true,
7379+
};
7380+
7381+
// Server receives headers.
7382+
assert_eq!(s.poll_server(), Ok((stream, ev_headers)));
7383+
assert_eq!(s.poll_server(), Err(Error::Done));
7384+
7385+
assert_eq!(s.client.stream_count(), init_streams_client + 1);
7386+
assert_eq!(s.server.stream_count(), init_streams_server + 1);
7387+
7388+
// Client sends body and fin
7389+
let body = s.send_body_client(stream, true).unwrap();
7390+
7391+
let mut recv_buf = vec![0; body.len()];
7392+
7393+
assert_eq!(s.poll_server(), Ok((stream, Event::Data)));
7394+
assert_eq!(s.recv_body_server(stream, &mut recv_buf), Ok(body.len()));
7395+
7396+
assert_eq!(s.poll_server(), Ok((stream, Event::Finished)));
7397+
7398+
assert_eq!(s.client.stream_count(), init_streams_client + 1);
7399+
assert_eq!(s.server.stream_count(), init_streams_server + 1);
7400+
7401+
// Server sends response and finishes the stream
7402+
let resp_headers = s.send_response(stream, false).unwrap();
7403+
let resp_body = s.send_body_server(stream, true).unwrap();
7404+
7405+
let mut resp_recv_buf = vec![0; resp_body.len()];
7406+
7407+
let ev_headers = Event::Headers {
7408+
list: resp_headers,
7409+
more_frames: true,
7410+
};
7411+
7412+
assert_eq!(s.poll_client(), Ok((stream, ev_headers)));
7413+
assert_eq!(s.poll_client(), Ok((stream, Event::Data)));
7414+
assert_eq!(s.recv_body_client(stream, &mut recv_buf), Ok(body.len()));
7415+
7416+
// The server stream should be gone now
7417+
assert_eq!(s.client.stream_count(), init_streams_client + 1);
7418+
assert_eq!(s.server.stream_count(), init_streams_server);
7419+
7420+
// Polling again should clean up the client
7421+
assert_eq!(s.poll_client(), Ok((stream, Event::Finished)));
7422+
assert_eq!(s.poll_client(), Err(Error::Done));
7423+
assert_eq!(s.client.stream_count(), init_streams_client);
7424+
}
73527425
}
73537426

73547427
#[cfg(feature = "ffi")]

quiche/src/stream/mod.rs

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use std::sync::Arc;
3131
use std::collections::hash_map;
3232
use std::collections::HashMap;
3333
use std::collections::HashSet;
34+
use std::collections::VecDeque;
3435

3536
use intrusive_collections::intrusive_adapter;
3637
use intrusive_collections::KeyAdapter;
@@ -98,6 +99,12 @@ pub struct StreamMap<F: BufFactory = DefaultBufFactory> {
9899
/// created streams, to prevent peers from re-creating them.
99100
collected: StreamIdHashSet,
100101

102+
/// Sequence of streams which have been completed, but not yet reported.
103+
///
104+
/// This is used to track streams that a higher layer may want to know
105+
/// have closed so that they can be cleaned up.
106+
pending_collected: VecDeque<u64>,
107+
101108
/// Peer's maximum bidirectional stream count limit.
102109
peer_max_streams_bidi: u64,
103110

@@ -557,6 +564,7 @@ impl<F: BufFactory> StreamMap<F> {
557564
self.remove_flushable(&s.priority_key);
558565

559566
self.collected.insert(stream_id);
567+
self.pending_collected.push_front(stream_id);
560568
}
561569

562570
/// Creates an iterator over streams that have outstanding data to read.
@@ -647,6 +655,12 @@ impl<F: BufFactory> StreamMap<F> {
647655
self.local_max_streams_uni - self.peer_opened_streams_uni
648656
}
649657

658+
/// Return streams we have collected but not yet notified the transport
659+
/// about.
660+
pub fn collected_streams<'a>(&'a mut self) -> impl Iterator<Item = u64> + 'a {
661+
self.pending_collected.drain(..)
662+
}
663+
650664
/// Returns the number of active streams in the map.
651665
#[cfg(test)]
652666
pub fn len(&self) -> usize {

0 commit comments

Comments
 (0)