Skip to content

[API Proposal]: QuicConnection.CancelPendingAcceptStream #60818

@JamesNK

Description

@JamesNK

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 AcceptStreamAsync call 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, AcceptStreamAsync will throw OperationAbortedException, 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions