Skip to content

HTTP2 doesn't retry the first stream on REFUSED_STREAM #44669

@BrennanConroy

Description

@BrennanConroy

We have an HttpClient+Kestrel test where we are checking that the stream limit prevents requests from hitting application function when the limit is reached.

The test is flaky and after investigating, it looks like if a batch of streams are sent in parallel when the connection starts and the stream limit is lower than the batch, then there is a race where the first request creates the connection but isn't the first stream sent and if it is for example the 6th stream with a server limit of 5, then it can get the REFUSED_STREAM response and will skip the retry logic because it has the isNewConnection value set to true.

catch (HttpRequestException e) when (!isNewConnection && e.AllowRetry == RequestRetryType.RetryOnSameOrNextProxy)

If the SETTINGS frame is received everything works fine, but because there is a big batch in the beginning, all the streams can be in-flight before the SETTINGS frame is received.

Test issue dotnet/aspnetcore#27379

cc @Tratcher and @halter73 who helped me understand some of the HTTP2 spec when looking at this.

Our test code looks something like this (server code is just pseudo code here):

server.MaxConcurrentStreams = 5;
var blockRequests = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
server.Run(async (request) =>
{
    // unblock after 5th request
    // ...
    await blockRequests.Task;
});

var tasks = new List<Task<HttpResponseMessage>>(10);
for (var i = 0; i < 10; i++)
{
    var requestTask = client.PostAsync(url, new StringContent("Hello World")).DefaultTimeout();
    tasks.Add(requestTask);
}

var responses = await Task.WhenAll(tasks.ToList()).DefaultTimeout(); // throws sometimes

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions