From 9ea2152571bbe48f0a819ebb545f62dcf7d1f738 Mon Sep 17 00:00:00 2001 From: Lucas Pardue Date: Sun, 6 Oct 2024 03:23:52 +0100 Subject: [PATCH] apps: more eagerly attempt to write HTTP response stream data in quiche-server Previously, the quiche-server HTTP/3 poll() loop would preference writing HTTP response headers for all received requests ahead of any other data. Once completed, no attempt to use remaining stream capacity was made. This had the effect of deferring writing HTTP response content until the next time the read loop was triggered. Especially noticeable when the path had a high RTT. This change forces an attempt to write on any writable response streams immediately after all response headers have been flushed. --- apps/src/bin/quiche-server.rs | 5 +++-- apps/src/common.rs | 28 ++++++++++++++++++++++++++-- 2 files changed, 29 insertions(+), 4 deletions(-) diff --git a/apps/src/bin/quiche-server.rs b/apps/src/bin/quiche-server.rs index add38a7ac0..028b2d49c2 100644 --- a/apps/src/bin/quiche-server.rs +++ b/apps/src/bin/quiche-server.rs @@ -495,8 +495,9 @@ fn main() { let http_conn = client.http_conn.as_mut().unwrap(); let partial_responses = &mut client.partial_responses; - // Handle writable streams. - for stream_id in conn.writable() { + // Visit all writable response streams to send any remaining HTTP + // content. + for stream_id in writable_response_streams(conn) { http_conn.handle_writable(conn, partial_responses, stream_id); } diff --git a/apps/src/common.rs b/apps/src/common.rs index 7cd474d8e2..1647eed723 100644 --- a/apps/src/common.rs +++ b/apps/src/common.rs @@ -358,6 +358,12 @@ pub trait HttpConn { ); } +pub fn writable_response_streams( + conn: &quiche::Connection, +) -> impl Iterator { + conn.writable().filter(|id| id % 4 == 0) +} + /// Represents an HTTP/0.9 formatted request. pub struct Http09Request { url: url::Url, @@ -675,7 +681,12 @@ impl HttpConn for Http09Conn { &mut self, conn: &mut quiche::Connection, partial_responses: &mut HashMap, stream_id: u64, ) { - trace!("{} stream {} is writable", conn.trace_id(), stream_id); + debug!( + "{} response stream {} is writable with capacity {:?}", + conn.trace_id(), + stream_id, + conn.stream_capacity(stream_id) + ); if !partial_responses.contains_key(&stream_id) { return; @@ -1393,6 +1404,9 @@ impl HttpConn for Http3Conn { index: &str, buf: &mut [u8], ) -> quiche::h3::Result<()> { // Process HTTP stream-related events. + // + // This loops over any and all received HTTP requests and sends just the + // HTTP response headers. loop { match self.h3_conn.poll(conn) { Ok((stream_id, quiche::h3::Event::Headers { list, .. })) => { @@ -1547,6 +1561,11 @@ impl HttpConn for Http3Conn { } } + // Visit all writable response streams to send HTTP content. + for stream_id in writable_response_streams(conn) { + self.handle_writable(conn, partial_responses, stream_id); + } + // Process datagram-related events. while let Ok(len) = conn.dgram_recv(buf) { let mut b = octets::Octets::with_slice(buf); @@ -1587,7 +1606,12 @@ impl HttpConn for Http3Conn { &mut self, conn: &mut quiche::Connection, partial_responses: &mut HashMap, stream_id: u64, ) { - debug!("{} stream {} is writable", conn.trace_id(), stream_id); + debug!( + "{} response stream {} is writable with capacity {:?}", + conn.trace_id(), + stream_id, + conn.stream_capacity(stream_id) + ); if !partial_responses.contains_key(&stream_id) { return;