-
Notifications
You must be signed in to change notification settings - Fork 4.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[API Proposal]: [QUIC] Support managing available streams on QuicConnection
#101534
Comments
Tagging subscribers to this area: @dotnet/ncl |
The event handler doesn't need that exact shape, so you can have one that doesn't allocate, e.g. public event Action<MyStruct> MyEvent;
public event Action<QuicConnection, QuicStreamType, int> MyEvent; This also lets you drop the I find it confusing to have both the
If this means dropping the properties and only exposing something like Is the fact that opening a stream is always async "just" a limitation of the MsQuic API, or a more fundamental limitation? |
None of our public APIs use this, we'd be first. I can put it up as an alternative design and let the API review committee decide.
It's not thread-safe.
What would be the value for the
Do you mean like your example you shared?
MsQuic limitation. MsQuic does all operations for one connection in a single thread from which notifies us about stream being opened. More details about opening streams:
We use the waiting method. More info: I experimented with using the failing one for the H3 Multiple Connections. But even with checking the available stream count and trying not to get to a point when OpenStream would fail, the occasional fail was detrimental to the perf. |
namespace System.Net.Quic;
// Existing class.
public abstract partial class QuicConnectionOptions
{
// New callback, raised when MAX_STREAMS frame arrives, reports new capacity for stream limits.
// First invocation with the initial values might happen during QuicConnection.ConnectAsync or QuicListener.AcceptConnectionAsync, before the QuicConnection object is handed out.
public Action<QuicConnection, QuicStreamCapacityChangedArgs>? StreamCapacityCallback { get; set; }
}
// Callback arguments:
public readonly struct QuicStreamCapacityChangedArgs
{
public int BidirectionalIncrement { get; init; }
public int UnidirectionalIncrement { get; init; }
} |
Should this be more generic for other types of information the server can send to the client? I see there is MAX_STREAMS, but there is also MAX_STREAMS_DATA and MAX_DATA. Do we care about those? Could we care about them in the future? Nothing is stopping us having this callback, and then adding a different callback for each type of frame, but is that the best API? Just want to think about the future. |
We discussed exactly this in the team. The disadvantage of a shared callback is that it'd get called for all frames/events regardless whether you're interested in all of them or not. Yes, it can be designed in a way that the user would specify which ones are interesting to them, but then you get even more complicated thing to set up and use. So we decided to go with the specific callback here.
At the moment we don't, we let MsQuic handle all the window updates and send buffering for us. Also the more interesting/actionable frame MAX_STREAM_DATA is per stream, so that'd require different callback anyway (unless the callback would mix stream and connection level events, which doesn't seem like a good idea). |
Background and motivation
Similarly to HTTP/2, we want to introduce support for multiple HTTP/3 connections (#101535). In order to properly manage the available streams count, we need the following API additions.
QUIC protocol has baked in stream multiplexing into the transport, meaning it handles stream creation and enforcement of stream limits. This is a difference from HTTP/2 where it's the application protocol that handles that.
QUIC stream limits are exposed as an ever increasing stream id and not as a stream count. For example, QUIC server will send that it accepts streams up to id 20. The client cannot open additional streams even after closing all of the streams, it has to wait until the server sends a new, incremented max stream id (see QUIC MAX_STREAMS frame). Which leads to the shape of the API change based on the protocol definition: callback to listen to newly released stream limit so that we can use the connection for new requests.
Original request for multiple HTTP/3 connections: #51775.
Draft PR with real usage: #101531.
API Proposal
API Usage
Simplified logic from
Http3Connection
:Alternative Designs
Using cumulative counts (not increments) in the
available(uni|bi)directionalStreamsCount
valuesWe could use the cumulative value for
availableBidirectionalStreamsCount
/availableUnidirectionalStreamsCount
(corresponding to MAX_STREAMS). And the user would keep the cumulative value of opened + reserved streams. Also the user would need to make sure to ignore any lower value as the callbacks might swap order.Callback arguments as individual parameters:
Most of our callback do not wrap args into structs, it's easier to consume, but it isn't in any way extendable.
Using Event / Callback property on
QuicConnection
The events are not very widespread tool used by .NET libraries. There are few in networking, but not much and nothing recent has been designed with them.
Using Tasks (alternative to events / callbacks)
It's possible to replace
StreamCapacityAvailable
with Tasks, one for each stream type:Disadvantage is that they have to be recreated and re-registered after each completion.
Risks
This is an advanced API that's being tailored mainly for H/3 multiple connection support (we will document this as such). The potential users must take care when designing logic around this and correctly keep the number of opened streams (decrementing the capacity).
The text was updated successfully, but these errors were encountered: