HTTP2: Track at least 100 closed streams (#24828)
This commit is contained in:
parent
82324bf357
commit
b8f906f0b8
|
|
@ -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))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
Loading…
Reference in New Issue