-
Notifications
You must be signed in to change notification settings - Fork 4.6k
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
[QUIC] Decide how to handle QUIC stream limits #32079
Comments
Why does HTTP3 need to be notified? I would assume that QuicConnection itself enforces the limits. That is, if we try to create a stream that exceeds the current limit, the first operation on that QuicStream will pend until the limit is increased (or cancellation occurs, of course). Is that not how it works currently? |
We also need to figure out the reverse issue -- meaning, how does a connection control and update its inbound stream limit. |
There are three scenarios I'd like to enable here. Just open me a streamThis is the most user-easy operation: just open a stream, and if none are available, wait for one to become available. using QuicStream stream = await quicConnection.OpenBidirectionalStreamAsync(cancellationToken);
// await only completes once a stream is available and reserved. Try to open a stream, without waitingThis is something useful for for both YARP and HttpClient. -- if a stream is available right now, let me use it. otherwise, fail immediately. This can be used to decide when to try a request against another server. using QuicStream? stream = quicConnection.OpenBidirectionalStreamIfAvailable();
if (stream is not null)
{
// stream is reserved for us and immediately available.
}
else
{
// a null return indicates no streams available -- maybe try your HTTP/3 request on a different IP that DNS/config provided.
} Allowing the caller to perform their own waitThis will be useful if the standard queuing behavior is not ideal. You could use it for diagnostics. A proxy might use this to start more aggressively load balancing to other hosts if the stream count is low. There is an inherent race condition here in that, when we tell the user they have X streams available, they may, by the time they get that notification, have already used those streams elsewhere, or the peer may have given us even more streams to use. This is something ultimately I don't think we can 100% solve for the user -- they will need to do some synchronization -- but having this plus the QuicConnection con = ...;
SemaphoreSlim sem = ...;
while(true)
{
// note: this tells you how many new streams can be opened, not a new "maximum stream count" value.
int newStreamCount = await con.WaitForAvailableBidirectionalStreamsAsync();
sem.Release(newStreamCount);
}
async Task<HttpResponseMessage> SendAsync(...)
{
await sem.WaitAsync(cancellationToken); // wait for a stream to become available.
// Stream limits can never be decreased in QUIC, and we are managing our own queuing, so this call should always succeed.
using QuicStream? stream = quicConnection.OpenBidirectionalStreamIfAvailable();
Debug.Assert(stream is not null);
// ... now send out the request.
} |
@scalablecory I still don't get the value of the last scenario "Allowing the caller to perform their own wait": For more aggressive load balancing strategies, I think that current number of available streams is more useful data point -- how close am I to the limit, so that I can choose between multiple options without any waiting. |
Meeting notes:
|
I'm YARP and I have 2 servers I'm load balancing against. I'm at my stream limit for both servers, so I need to wait. I don't want to pick a connection and wait -- I want to wait for the first connection to become available. Maybe when streams become available, I don't want to wake up requests in a strictly FIFO order, but instead want to prioritize them.
QUIC doesn't work this way -- In HTTP/2, you configure a maximum stream count and when one stream finishes you can open a new one. In QUIC, a stream finishing doesn't mean you can open a new one. The remote side must send you a new chunk of streams to use. It is very similar to how HTTP/2 handles receive window updates. |
Triage: @ManickaP is doing research on this right now, will have opinions later. |
Notes:
Additional thoughts:
|
API suggestions:
|
Triage: The MVP here is something like scenario 3 outlined in #32079 (comment) We should consider if we need the other APIs -- if we suspect scenario 3 will be rare, or too difficult for the average QUIC user, then we should think about adding easier APIs. |
How does the msquic API work here? I assume there is some way to get notified when the peer makes more streams available, right? |
Yes, when the peer increases the MAX_STREAMS we get a notification. We can hook this up to this event. When I wrote it, I was thinking about what this method promises and whether it can deliver it, which depends on synchronizing your work on the connection. |
Small thought: Above, we have I'm not sure this is useful. I suspect you usually just care that there is an available stream. And if you care about the exact available count, you can just get this yourself. It's immediately out of date anyway, so I expect the only thing people really care about is whether it's > 0 or not. That said, I do think |
Above proposal returns the number of new streams available, not total count available. it's so you can do a very simple The confusion over what the count is here tells me it would need either a name change or do as you propose. We could move the semaphore inside of QuicConnection, I guess. We'll want to think if this would limit any designs that might have used a different mechanism for queuing things.
Bool seems fine. if we keep it returning the new stream count, it could just return 0 to indicate end of connection. |
Implements the 3rd option Allowing the caller to perform their own wait from #32079 (comment) Adds WaitForAvailable(Bidi|Uni)rectionalStreamsAsync: - triggered by peer announcement about new streams (QUIC_CONNECTION_EVENT_TYPE.STREAMS_AVAILABLE) - if the connection is closed/disposed, the method throws QuicConnectionAbortedException which fitted our H3 better than boolean (can be changed) Changes stream limit type to int
How should we be validating these limits?
runtime/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicClientConnectionOptions.cs
Lines 29 to 43 in eab397d
How do we get notified that a connection's stream limit has changed(MAX_STREAMS frame event)? This is required for HTTP/3
runtime/src/libraries/System.Net.Quic/src/System/Net/Quic/QuicConnection.cs
Lines 89 to 97 in eab397d
runtime/src/libraries/System.Net.Http/src/System/Net/Http/SocketsHttpHandler/Http3Connection.cs
Lines 235 to 256 in eab397d
The text was updated successfully, but these errors were encountered: