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 Http2KeepAlive _keepAlive;
|
||||||
internal readonly Dictionary<int, Http2Stream> _streams = new Dictionary<int, Http2Stream>();
|
internal readonly Dictionary<int, Http2Stream> _streams = new Dictionary<int, Http2Stream>();
|
||||||
internal Http2StreamStack StreamPool;
|
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 InitialStreamPoolSize = 5;
|
||||||
internal const int MaxStreamPoolSize = 100;
|
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.
|
// 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.
|
// 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.
|
// 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.
|
// Server is getting hit hard with connection resets.
|
||||||
// Tell client to calm down.
|
// 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.
|
// Compare to UpdateCompletedStreams, but only removes streams if over the max stream drain limit.
|
||||||
private void MakeSpaceInDrainQueue()
|
private void MakeSpaceInDrainQueue()
|
||||||
{
|
{
|
||||||
var maxStreams = _serverSettings.MaxConcurrentStreams * 2;
|
var maxStreams = MaxTrackedStreams;
|
||||||
// If we're tracking too many streams, discard the oldest.
|
// If we're tracking too many streams, discard the oldest.
|
||||||
while (_streams.Count >= maxStreams && _completedStreams.TryDequeue(out var stream))
|
while (_streams.Count >= maxStreams && _completedStreams.TryDequeue(out var stream))
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1460,29 +1460,82 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Frame_MultipleStreams_RequestsNotFinished_EnhanceYourCalm()
|
public async Task MaxTrackedStreams_SmallMaxConcurrentStreams_LowerLimitOf100Async()
|
||||||
{
|
{
|
||||||
_serviceContext.ServerOptions.Limits.Http2.MaxStreamsPerConnection = 1;
|
_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);
|
var tcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
|
||||||
await InitializeConnectionAsync(async context =>
|
await InitializeConnectionAsync(async context =>
|
||||||
{
|
{
|
||||||
await tcs.Task.DefaultTimeout();
|
await tcs.Task.DefaultTimeout();
|
||||||
});
|
});
|
||||||
|
|
||||||
await StartStreamAsync(1, _browserRequestHeaders, endStream: false);
|
var streamId = 1;
|
||||||
await SendRstStreamAsync(1);
|
for (var i = 0; i < sentStreams - 1; i++)
|
||||||
await StartStreamAsync(3, _browserRequestHeaders, endStream: true);
|
{
|
||||||
await SendRstStreamAsync(3);
|
await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true);
|
||||||
await StartStreamAsync(5, _browserRequestHeaders, endStream: true);
|
await SendRstStreamAsync(streamId);
|
||||||
|
|
||||||
|
streamId += 2;
|
||||||
|
}
|
||||||
|
|
||||||
|
await StartStreamAsync(streamId, _browserRequestHeaders, endStream: true);
|
||||||
await WaitForStreamErrorAsync(
|
await WaitForStreamErrorAsync(
|
||||||
expectedStreamId: 5,
|
expectedStreamId: streamId,
|
||||||
expectedErrorCode: Http2ErrorCode.ENHANCE_YOUR_CALM,
|
expectedErrorCode: Http2ErrorCode.ENHANCE_YOUR_CALM,
|
||||||
expectedErrorMessage: CoreStrings.Http2TellClientToCalmDown);
|
expectedErrorMessage: CoreStrings.Http2TellClientToCalmDown);
|
||||||
|
|
||||||
tcs.SetResult();
|
tcs.SetResult();
|
||||||
|
|
||||||
await StopConnectionAsync(5, ignoreNonGoAwayFrames: false);
|
await StopConnectionAsync(streamId, ignoreNonGoAwayFrames: false);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue