-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Description
Background and motivation
There is an QuicConnection.AcceptStreamAsync(CancellationToken) method for accepting new streams from the peer. The main HTTP/3 connection logic loop in Kestrel has a call to this method.
Most of the time in an HTTP/3 connection in Kestrel is either waiting on a new stream from the peer (i.e. awaiting AcceptStreamAsync) or processing the stream returned from that method. However, occasionally new events are raised that we want the HTTP/3 connection to react to.
To keep things simple in Kestrel, we want connection logic to stay on a single thread. That means connection-related events, such as a request to end the connection because the server is shutting down, are processed as part of the main connection loop. To do this we want to stop waiting for AcceptStreamAsync, process updates from other events, then resume waiting on AcceptStreamAsync (or exit if the event is to shutdown the connection).
Today we are achieving this design by using a CancellationTokenSource and passing the CancellationToken to AcceptStreamAsync. An event will cancel the token, do processing, then create a new CTS. The problem is this is quite expensive:
- Every
AcceptStreamAsynccall will be passed a CT that it needs to subscribe and unsubscribe to. This is the hot path for connection processing in Kestrel. Avoiding this overhead to maximize requests per second is desirable. - Every time there is a new connection event the CTS will be canceled,
AcceptStreamAsyncwill throwOperationAbortedException, and a new CTS will be recreated. Reducing this overhead isn't as important because events aren't common, but it would be nice to avoid throwing exceptions and allocations if possible.
API Proposal
namespace System.Net.Quic
{
public class QuicConnection
{
public void CancelPendingAcceptStream();
}
}The method will behave like PipeReader.CancelPendingRead().
When called, the current (or next) call to AcceptStreamAsync will immediately return null.
API Usage
var connection = await GetQuicConnectionAsync();
var newStreamTask = connection.AcceptStreamAsync();
connection.CancelPendingAcceptStream();
var stream = await newStreamTask;Alternative Designs
No response
Risks
No response