HTTP2: Track at least 100 closed streams (#24828)

This commit is contained in:
James Newton-King 2020-08-13 20:44:52 +12:00 committed by GitHub
parent 82324bf357
commit b8f906f0b8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 67 additions and 10 deletions

View File

@ -69,6 +69,10 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
internal readonly Http2KeepAlive _keepAlive;
internal readonly Dictionary<int, Http2Stream> _streams = new Dictionary<int, Http2Stream>();
internal Http2StreamStack StreamPool;
// Max tracked streams is double max concurrent streams.
// If a small MaxConcurrentStreams value is configured then still track at least to 100 streams
// to support clients that send a burst of streams while the connection is being established.
internal uint MaxTrackedStreams => Math.Max(_serverSettings.MaxConcurrentStreams * 2, 100);
internal const int InitialStreamPoolSize = 5;
internal const int MaxStreamPoolSize = 100;
@ -1032,7 +1036,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// We don't use the _serverActiveRequestCount here as during shutdown, it and the dictionary counts get out of sync.
// The streams still exist in the dictionary until the client responds with a RST or END_STREAM.
// Also, we care about the dictionary size for too much memory consumption.
if (_streams.Count > _serverSettings.MaxConcurrentStreams * 2)
if (_streams.Count > MaxTrackedStreams)
{
// Server is getting hit hard with connection resets.
// Tell client to calm down.
@ -1166,7 +1170,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
// Compare to UpdateCompletedStreams, but only removes streams if over the max stream drain limit.
private void MakeSpaceInDrainQueue()
{
var maxStreams = _serverSettings.MaxConcurrentStreams * 2;
var maxStreams = MaxTrackedStreams;
// If we're tracking too many streams, discard the oldest.
while (_streams.Count >= maxStreams && _completedStreams.TryDequeue(out var stream))
{

View File

@ -1460,29 +1460,82 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
}
[Fact]
public async Task Frame_MultipleStreams_RequestsNotFinished_EnhanceYourCalm()
public async Task MaxTrackedStreams_SmallMaxConcurrentStreams_LowerLimitOf100Async()
{
_serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = 1;
await InitializeConnectionAsync(_noopApplication);
Assert.Equal((uint)100, _connection.MaxTrackedStreams);
await StopConnectionAsync(0, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task MaxTrackedStreams_DefaultMaxConcurrentStreams_DoubleLimit()
{
_serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = 100;
await InitializeConnectionAsync(_noopApplication);
Assert.Equal((uint)200, _connection.MaxTrackedStreams);
await StopConnectionAsync(0, ignoreNonGoAwayFrames: false);
}
[Fact]
public async Task MaxTrackedStreams_LargeMaxConcurrentStreams_DoubleLimit()
{
_serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = int.MaxValue;
await InitializeConnectionAsync(_noopApplication);
Assert.Equal((uint)int.MaxValue * 2, _connection.MaxTrackedStreams);
await StopConnectionAsync(0, ignoreNonGoAwayFrames: false);
}
[Fact]
public Task Frame_MultipleStreams_RequestsNotFinished_LowMaxStreamsPerConnection_EnhanceYourCalmAfter100()
{
// Kestrel always tracks at least 100 streams
return RequestUntilEnhanceYourCalm(maxStreamsPerConnection: 1, sentStreams: 101);
}
[Fact]
public Task Frame_MultipleStreams_RequestsNotFinished_DefaultMaxStreamsPerConnection_EnhanceYourCalmAfterDoubleMaxStreams()
{
// Kestrel tracks max streams per connection * 2
return RequestUntilEnhanceYourCalm(maxStreamsPerConnection: 100, sentStreams: 201);
}
private async Task RequestUntilEnhanceYourCalm(int maxStreamsPerConnection, int sentStreams)
{
_serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = maxStreamsPerConnection;
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
await InitializeConnectionAsync(async context =>
{
await tcs.Task.DefaultTimeout();
});
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
await SendRstStreamAsync(1);
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
await SendRstStreamAsync(3);
await StartStreamAsync(5, _browserRequestHeaders, endStream: true);
var streamId = 1;
for (var i = 0; i < sentStreams - 1; i++)
{
await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true);
await SendRstStreamAsync(streamId);
streamId += 2;
}
await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true);
await WaitForStreamErrorAsync(
expectedStreamId: 5,
expectedStreamId: streamId,
expectedErrorCode: Http2ErrorCode.ENHANCE_YOUR_CALM,
expectedErrorMessage: CoreStrings.Http2TellClientToCalmDown);
tcs.SetResult();
await StopConnectionAsync(5, ignoreNonGoAwayFrames: false);
await StopConnectionAsync(streamId, ignoreNonGoAwayFrames: false);
}
[Fact]