Skip to content

Conversation

@LPardue
Copy link
Contributor

@LPardue LPardue commented Sep 2, 2025

NB: this is an alternative to #2147, it adds streaming to new functions in order to keep previous behaviour. New functions names can be bikeshedded.

Calls to send_request(), send_response(), send_response_with_priority(),
and send_additional_headers() can fail with a StreamBlocked
error indicating lack of underlying transport capacity. When this
occurs, applications ar expected to retry the operation when
the stream is later reported as writable.

However, certain conditions could mean that sufficient capacity
might never be made available, effectively permenantly blocking
header sends. The root cause of this problem was the choice to enforce
that a HEADERS frame is always sent (buffered into a quiche stream)
in whole.

This change adds new variants of the header sending functions that
support a streaming send design. These will produce a HEADERS frame
that can be sent in whole or in part, depending on available capacity.
When a frame is only partly sent, applications are notified and
can resume sending using the new continue_partial_headers()
method, once the stream is writable.

While headers are being streamed, other operations that
would cause an HTTP/3 frame to be sent on the stream are
prevented. HEADERS frames must be sent completly before
other operations are successful.

Applications do not need to manage the partial HEADERS
buffer, this is dealt with inside quiche.

Calls to send_request(), send_response(), send_response_with_priority(),
and send_additional_headers() can fail with a StreamBlocked
error indicating lack of underlying transport capacity. When this
occurs, applications ar expected to retry the operation when
the stream is later reported as writable.

However, certain conditions could mean that sufficient capacity
might never be made available, effectively permenantly blocking
header sends. The root cause of this problem was the choice to enforce
that a HEADERS frame is always sent (buffered into a quiche stream)
in whole.

This change adds new variants of the header sending functions that
support a streaming send design. These will produce a HEADERS frame
that can be sent in whole or in part, depending on available capacity.
When a frame is only partly sent, applications are notified and
can resume sending using the new continue_partial_headers()
method, once the stream is writable.

While headers are being streamed, other operations that
would cause an HTTP/3 frame to be sent on the stream are
prevented. HEADERS frames must be sent completly before
other operations are successful.

Applications do not need to manage the partial HEADERS
buffer, this is dealt with inside quiche.
match conn.stream_writable(stream_id, overhead + header_block.len()) {
Ok(true) => (),
stream.outgoing_frame_header = frame_header;
stream.outgoing_frame_header_off = 0;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: when streaming mode is false, don't set these

}
stream.outgoing_frame_payload = frame_payload;
stream.outgoing_frame_payload_off = 0;
stream.fin_after_outgoing_frame = fin;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: when streaming mode is false, don't set these


match conn.stream_send(stream_id, &stream.outgoing_frame_header, false) {
Ok(v) =>
if v != stream.outgoing_frame_header.len() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: only do this when streaming mode is true

stream.fin_after_outgoing_frame,
) {
Ok(v) =>
if v != stream.outgoing_frame_payload.len() {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

TODO: only do this when streaming mode is true

@LPardue LPardue force-pushed the lucas/streaming-h3-header-sends-new-api branch from 3c78e8c to 468a67e Compare September 2, 2025 01:51
@gregor-cf
Copy link
Contributor

gregor-cf commented Sep 3, 2025

It's probably a good idea to keep the old functions and add new ones like this PR. With #2147 the functions' contract would change but not their signature, so code using them would still compile which could lead to unexpected errors and bugs down the line.

I'd probably add a #[deprecated(note="use the new foo() function")] attribute to the old functions though.

you can avoid the code duplication if you add private implementations of your functions like:

pub fn send_response(....) {
     send_response_impl(false)
}
pub fn send_response2(...) {
     send_response_impl(true)
}
fn send_response_impl(..., streaming_mode: bool)

@LPardue
Copy link
Contributor Author

LPardue commented Sep 9, 2025

Closing this in favor of #2153

@LPardue LPardue closed this Sep 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants