diff --git a/NuGet.config b/NuGet.config
index 9fede3245833..d37b12d22a3c 100644
--- a/NuGet.config
+++ b/NuGet.config
@@ -4,11 +4,12 @@
-
+
-
-
+
+
+
@@ -26,11 +27,12 @@
-
-
+
+
+
-
+
diff --git a/eng/Baseline.Designer.props b/eng/Baseline.Designer.props
index 0b962449d9f1..0ca47e957b58 100644
--- a/eng/Baseline.Designer.props
+++ b/eng/Baseline.Designer.props
@@ -2,28 +2,28 @@
$(MSBuildAllProjects);$(MSBuildThisFileFullPath)
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
-
-
+
+
+
@@ -34,120 +34,120 @@
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
-
+
+
- 6.0.22
+ 6.0.23
-
-
+
+
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
+
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
@@ -155,114 +155,114 @@
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
-
+
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
+
- 6.0.22
+ 6.0.23
-
-
+
+
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
-
+
+
- 6.0.22
+ 6.0.23
-
+
- 6.0.22
+ 6.0.23
-
-
-
+
+
+
- 6.0.22
+ 6.0.23
-
-
+
+
- 6.0.22
+ 6.0.23
-
-
+
+
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
-
+
+
@@ -270,7 +270,7 @@
- 6.0.22
+ 6.0.23
@@ -278,50 +278,50 @@
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
-
-
+
+
@@ -331,8 +331,8 @@
-
-
+
+
@@ -340,8 +340,8 @@
-
-
+
+
@@ -352,58 +352,58 @@
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
-
+
+
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
-
+
- 6.0.22
+ 6.0.23
@@ -411,71 +411,71 @@
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
-
-
+
+
- 6.0.22
+ 6.0.23
-
-
+
+
- 6.0.22
+ 6.0.23
@@ -491,195 +491,195 @@
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
+
- 6.0.22
+ 6.0.23
-
-
+
+
- 6.0.22
+ 6.0.23
-
-
+
+
- 6.0.22
+ 6.0.23
-
+
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
+
- 6.0.22
+ 6.0.23
-
-
+
+
-
-
+
+
-
-
+
+
- 6.0.22
+ 6.0.23
-
-
+
+
-
-
+
+
-
-
+
+
-
-
+
+
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
-
-
-
-
+
+
+
+
- 6.0.22
+ 6.0.23
@@ -688,69 +688,69 @@
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
+
- 6.0.22
+ 6.0.23
-
+
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
@@ -769,7 +769,7 @@
- 6.0.22
+ 6.0.23
@@ -788,7 +788,7 @@
- 6.0.22
+ 6.0.23
@@ -804,46 +804,46 @@
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
-
-
+
+
+
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
@@ -853,7 +853,7 @@
- 6.0.22
+ 6.0.23
@@ -862,73 +862,73 @@
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
-
+
-
+
-
+
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
@@ -957,11 +957,11 @@
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
@@ -979,13 +979,13 @@
- 6.0.22
+ 6.0.23
- 6.0.22
+ 6.0.23
-
+
\ No newline at end of file
diff --git a/eng/Baseline.xml b/eng/Baseline.xml
index 2984eef6c6e4..749cc484d04a 100644
--- a/eng/Baseline.xml
+++ b/eng/Baseline.xml
@@ -4,111 +4,111 @@ This file contains a list of all the packages and their versions which were rele
Update this list when preparing for a new patch.
-->
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml
index 66cdb6dd76f1..86b6b375a58b 100644
--- a/eng/Version.Details.xml
+++ b/eng/Version.Details.xml
@@ -9,37 +9,37 @@
-->
-
- https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 687fc8f9a538592b2b1494f6353293aac5da74a5
+
+ https://github.com/dotnet/efcore
+ 50a6896cca24b06e154bdf652c2e7aefd0dfb7a1
-
- https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 687fc8f9a538592b2b1494f6353293aac5da74a5
+
+ https://github.com/dotnet/efcore
+ 50a6896cca24b06e154bdf652c2e7aefd0dfb7a1
-
- https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 687fc8f9a538592b2b1494f6353293aac5da74a5
+
+ https://github.com/dotnet/efcore
+ 50a6896cca24b06e154bdf652c2e7aefd0dfb7a1
-
- https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 687fc8f9a538592b2b1494f6353293aac5da74a5
+
+ https://github.com/dotnet/efcore
+ 50a6896cca24b06e154bdf652c2e7aefd0dfb7a1
-
- https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 687fc8f9a538592b2b1494f6353293aac5da74a5
+
+ https://github.com/dotnet/efcore
+ 50a6896cca24b06e154bdf652c2e7aefd0dfb7a1
-
- https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 687fc8f9a538592b2b1494f6353293aac5da74a5
+
+ https://github.com/dotnet/efcore
+ 50a6896cca24b06e154bdf652c2e7aefd0dfb7a1
-
- https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 687fc8f9a538592b2b1494f6353293aac5da74a5
+
+ https://github.com/dotnet/efcore
+ 50a6896cca24b06e154bdf652c2e7aefd0dfb7a1
-
- https://dev.azure.com/dnceng/internal/_git/dotnet-efcore
- 687fc8f9a538592b2b1494f6353293aac5da74a5
+
+ https://github.com/dotnet/efcore
+ 50a6896cca24b06e154bdf652c2e7aefd0dfb7a1
https://github.com/dotnet/runtime
@@ -177,9 +177,9 @@
https://github.com/dotnet/runtime
4822e3c3aa77eb82b2fb33c9321f923cf11ddde6
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 4bb6dc195c0a3bc4c7e24ff54a8925b98db4fecd
+ e0f0de876a67755a2c6cd2dc730c13f5959bdea8
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
@@ -245,33 +245,33 @@
https://github.com/dotnet/runtime
4822e3c3aa77eb82b2fb33c9321f923cf11ddde6
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 4bb6dc195c0a3bc4c7e24ff54a8925b98db4fecd
+ e0f0de876a67755a2c6cd2dc730c13f5959bdea8
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 4bb6dc195c0a3bc4c7e24ff54a8925b98db4fecd
+ e0f0de876a67755a2c6cd2dc730c13f5959bdea8
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 4bb6dc195c0a3bc4c7e24ff54a8925b98db4fecd
+ e0f0de876a67755a2c6cd2dc730c13f5959bdea8
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 4bb6dc195c0a3bc4c7e24ff54a8925b98db4fecd
+ e0f0de876a67755a2c6cd2dc730c13f5959bdea8
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 4bb6dc195c0a3bc4c7e24ff54a8925b98db4fecd
+ e0f0de876a67755a2c6cd2dc730c13f5959bdea8
-
+
https://dev.azure.com/dnceng/internal/_git/dotnet-runtime
- 4bb6dc195c0a3bc4c7e24ff54a8925b98db4fecd
+ e0f0de876a67755a2c6cd2dc730c13f5959bdea8
diff --git a/eng/Versions.props b/eng/Versions.props
index 84aeccf24dce..2186c0e1d1eb 100644
--- a/eng/Versions.props
+++ b/eng/Versions.props
@@ -9,7 +9,7 @@
6
0
24
- false
+ true
@@ -63,12 +63,12 @@
6.0.0
- 6.0.22
- 6.0.22
- 6.0.22
- 6.0.22
- 6.0.22
- 6.0.22-servicing.23424.25
+ 6.0.23
+ 6.0.23
+ 6.0.23
+ 6.0.23
+ 6.0.23
+ 6.0.23-servicing.23480.2
6.0.0
6.0.1
6.0.0
@@ -103,7 +103,7 @@
6.0.0
6.0.0
6.0.0
- 6.0.22-servicing.23424.25
+ 6.0.23-servicing.23480.2
6.0.1
6.0.0
6.0.2
@@ -122,14 +122,14 @@
6.0.11
- 6.0.22
- 6.0.22
- 6.0.22
- 6.0.22
- 6.0.22
- 6.0.22
- 6.0.22
- 6.0.22
+ 6.0.23
+ 6.0.23
+ 6.0.23
+ 6.0.23
+ 6.0.23
+ 6.0.23
+ 6.0.23
+ 6.0.23
6.0.0-beta.23408.5
6.0.0-beta.23408.5
diff --git a/global.json b/global.json
index 4cc2acb633b1..83661f95122b 100644
--- a/global.json
+++ b/global.json
@@ -1,9 +1,9 @@
{
"sdk": {
- "version": "6.0.122"
+ "version": "6.0.123"
},
"tools": {
- "dotnet": "6.0.122",
+ "dotnet": "6.0.123",
"runtimes": {
"dotnet/x64": [
"2.1.30",
diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs
index ac3e9d166d4d..2e4a208fcc4e 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2Connection.cs
@@ -33,6 +33,55 @@ internal partial class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeade
private const PseudoHeaderFields _mandatoryRequestPseudoHeaderFields =
PseudoHeaderFields.Method | PseudoHeaderFields.Path | PseudoHeaderFields.Scheme;
+ private const string MaximumEnhanceYourCalmCountProperty = "Microsoft.AspNetCore.Server.Kestrel.Http2.MaxEnhanceYourCalmCount";
+ private const string MaximumFlowControlQueueSizeProperty = "Microsoft.AspNetCore.Server.Kestrel.Http2.MaxConnectionFlowControlQueueSize";
+
+ private static readonly int _enhanceYourCalmMaximumCount = GetMaximumEnhanceYourCalmCount();
+
+ private static int GetMaximumEnhanceYourCalmCount()
+ {
+ var data = AppContext.GetData(MaximumEnhanceYourCalmCountProperty);
+ if (data is int count)
+ {
+ return count;
+ }
+ if (data is string countStr && int.TryParse(countStr, out var parsed))
+ {
+ return parsed;
+ }
+
+ return 20; // Empirically derived
+ }
+
+ // Accumulate _enhanceYourCalmCount over the course of EnhanceYourCalmTickWindowCount ticks.
+ // This should make bursts less likely to trigger disconnects.
+ private const int EnhanceYourCalmTickWindowCount = 5;
+
+ private static bool IsEnhanceYourCalmEnabled => _enhanceYourCalmMaximumCount > 0;
+
+ private static readonly int? ConfiguredMaximumFlowControlQueueSize = GetConfiguredMaximumFlowControlQueueSize();
+
+ private static int? GetConfiguredMaximumFlowControlQueueSize()
+ {
+ var data = AppContext.GetData(MaximumFlowControlQueueSizeProperty);
+
+ if (data is int count)
+ {
+ return count;
+ }
+
+ if (data is string countStr && int.TryParse(countStr, out var parsed))
+ {
+ return parsed;
+ }
+
+ return null;
+ }
+
+ private readonly int _maximumFlowControlQueueSize;
+
+ private bool IsMaximumFlowControlQueueSizeEnabled => _maximumFlowControlQueueSize > 0;
+
private readonly HttpConnectionContext _context;
private readonly Http2FrameWriter _frameWriter;
private readonly Pipe _input;
@@ -40,7 +89,8 @@ internal partial class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeade
private readonly int _minAllocBufferSize;
private readonly HPackDecoder _hpackDecoder;
private readonly InputFlowControl _inputFlowControl;
- private readonly OutputFlowControl _outputFlowControl = new OutputFlowControl(new MultipleAwaitableProvider(), Http2PeerSettings.DefaultInitialWindowSize);
+ private readonly OutputFlowControl _outputFlowControl;
+ private readonly AwaitableProvider _outputFlowControlAwaitableProvider; // Keep our own reference so we can track queue size
private readonly Http2PeerSettings _serverSettings = new Http2PeerSettings();
private readonly Http2PeerSettings _clientSettings = new Http2PeerSettings();
@@ -59,6 +109,9 @@ internal partial class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeade
private int _clientActiveStreamCount;
private int _serverActiveStreamCount;
+ private int _enhanceYourCalmCount;
+ private int _tickCount;
+
// The following are the only fields that can be modified outside of the ProcessRequestsAsync loop.
private readonly ConcurrentQueue _completedStreams = new ConcurrentQueue();
private readonly StreamCloseAwaitable _streamCompletionAwaitable = new StreamCloseAwaitable();
@@ -88,6 +141,9 @@ public Http2Connection(HttpConnectionContext context)
// Capture the ExecutionContext before dispatching HTTP/2 middleware. Will be restored by streams when processing request
_context.InitialExecutionContext = ExecutionContext.Capture();
+ _outputFlowControlAwaitableProvider = new MultipleAwaitableProvider();
+ _outputFlowControl = new OutputFlowControl(_outputFlowControlAwaitableProvider, Http2PeerSettings.DefaultInitialWindowSize);
+
_frameWriter = new Http2FrameWriter(
context.Transport.Output,
context.ConnectionContext,
@@ -129,6 +185,17 @@ public Http2Connection(HttpConnectionContext context)
_serverSettings.MaxHeaderListSize = (uint)httpLimits.MaxRequestHeadersTotalSize;
_serverSettings.InitialWindowSize = (uint)http2Limits.InitialStreamWindowSize;
+ _maximumFlowControlQueueSize = ConfiguredMaximumFlowControlQueueSize is null
+ ? 4 * http2Limits.MaxStreamsPerConnection // 4 is a magic number to give us some padding above the expected maximum size
+ : (int)ConfiguredMaximumFlowControlQueueSize;
+
+ var minimumMaximumFlowControlQueueSize = 2 * http2Limits.MaxStreamsPerConnection; // Double to match 7.0 and 8.0
+ if (IsMaximumFlowControlQueueSizeEnabled && _maximumFlowControlQueueSize < minimumMaximumFlowControlQueueSize)
+ {
+ Log.Http2FlowControlQueueMaximumTooLow(context.ConnectionId, minimumMaximumFlowControlQueueSize, _maximumFlowControlQueueSize);
+ _maximumFlowControlQueueSize = minimumMaximumFlowControlQueueSize;
+ }
+
// Start pool off at a smaller size if the max number of streams is less than the InitialStreamPoolSize
StreamPool = new PooledStreamStack(Math.Min(InitialStreamPoolSize, http2Limits.MaxStreamsPerConnection));
@@ -352,13 +419,20 @@ public async Task ProcessRequestsAsync(IHttpApplication appl
stream.Abort(new IOException(CoreStrings.Http2StreamAborted, connectionError));
}
- // Use the server _serverActiveStreamCount to drain all requests on the server side.
- // Can't use _clientActiveStreamCount now as we now decrement that count earlier/
- // Can't use _streams.Count as we wait for RST/END_STREAM before removing the stream from the dictionary
- while (_serverActiveStreamCount > 0)
+ // For some reason, this loop doesn't terminate when we're trying to abort.
+ // Since we're making a narrow fix for a patch, we'll bypass it in such scenarios.
+ // TODO: This is probably a bug - something in here should probably detect aborted
+ // connections and short-circuit.
+ if (!(IsEnhanceYourCalmEnabled || IsMaximumFlowControlQueueSizeEnabled) || error is not Http2ConnectionErrorException)
{
- await _streamCompletionAwaitable;
- UpdateCompletedStreams();
+ // Use the server _serverActiveStreamCount to drain all requests on the server side.
+ // Can't use _clientActiveStreamCount now as we now decrement that count earlier/
+ // Can't use _streams.Count as we wait for RST/END_STREAM before removing the stream from the dictionary
+ while (_serverActiveStreamCount > 0)
+ {
+ await _streamCompletionAwaitable;
+ UpdateCompletedStreams();
+ }
}
while (StreamPool.TryPop(out var pooledStream))
@@ -1053,6 +1127,20 @@ private void StartStream()
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2ErrorMaxStreams, Http2ErrorCode.REFUSED_STREAM);
}
+ if (IsMaximumFlowControlQueueSizeEnabled && _outputFlowControlAwaitableProvider.ActiveCount > _maximumFlowControlQueueSize)
+ {
+ Log.Http2FlowControlQueueOperationsExceeded(_context.ConnectionId, _maximumFlowControlQueueSize);
+
+ // Now that we've logged a useful message, we can put vague text in the exception
+ // messages in case they somehow make it back to the client (not expected)
+
+ // This will close the socket - we want to do that right away
+ Abort(new ConnectionAbortedException("HTTP/2 connection exceeded the outgoing flow control maximum queue size."));
+
+ // Throwing an exception as well will help us clean up on our end more quickly by (e.g.) skipping processing of already-buffered input
+ throw new Http2ConnectionErrorException(CoreStrings.Http2ConnectionFaulted, Http2ErrorCode.INTERNAL_ERROR);
+ }
+
// 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.
@@ -1061,6 +1149,20 @@ private void StartStream()
// Server is getting hit hard with connection resets.
// Tell client to calm down.
// TODO consider making when to send ENHANCE_YOUR_CALM configurable?
+
+ if (IsEnhanceYourCalmEnabled && Interlocked.Increment(ref _enhanceYourCalmCount) > EnhanceYourCalmTickWindowCount * _enhanceYourCalmMaximumCount)
+ {
+ Log.Http2TooManyEnhanceYourCalms(_context.ConnectionId, _enhanceYourCalmMaximumCount);
+
+ // Now that we've logged a useful message, we can put vague text in the exception
+ // messages in case they somehow make it back to the client (not expected)
+
+ // This will close the socket - we want to do that right away
+ Abort(new ConnectionAbortedException(CoreStrings.Http2ConnectionFaulted));
+ // Throwing an exception as well will help us clean up on our end more quickly by (e.g.) skipping processing of already-buffered input
+ throw new Http2ConnectionErrorException(CoreStrings.Http2ConnectionFaulted, Http2ErrorCode.ENHANCE_YOUR_CALM);
+ }
+
throw new Http2StreamErrorException(_currentHeadersStream.StreamId, CoreStrings.Http2TellClientToCalmDown, Http2ErrorCode.ENHANCE_YOUR_CALM);
}
}
@@ -1123,6 +1225,12 @@ private void AbortStream(int streamId, IOException error)
void IRequestProcessor.Tick(DateTimeOffset now)
{
Input.CancelPendingRead();
+ // We count EYCs over a window of a given length to avoid flagging short-lived bursts.
+ // At the end of each window, reset the count.
+ if (IsEnhanceYourCalmEnabled && ++_tickCount % EnhanceYourCalmTickWindowCount == 0)
+ {
+ Interlocked.Exchange(ref _enhanceYourCalmCount, 0);
+ }
}
void IHttp2StreamLifetimeHandler.OnStreamCompleted(Http2Stream stream)
diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs
index 6a75f4657b14..28b7b5cd8b05 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/IKestrelTrace.cs
@@ -78,8 +78,14 @@ internal interface IKestrelTrace : ILogger
void Http2FrameSending(string connectionId, Http2Frame frame);
+ void Http2TooManyEnhanceYourCalms(string connectionId, int count);
+
void Http2MaxConcurrentStreamsReached(string connectionId);
+ void Http2FlowControlQueueOperationsExceeded(string connectionId, int count);
+
+ void Http2FlowControlQueueMaximumTooLow(string connectionId, int expected, int actual);
+
void InvalidResponseHeaderRemoved();
void Http3ConnectionError(string connectionId, Http3ConnectionErrorException ex);
diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs
index 3c3b7bc4af07..c1a6021549f5 100644
--- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs
+++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/KestrelTrace.cs
@@ -395,6 +395,30 @@ public void Http3GoAwayStreamId(string connectionId, long goAwayStreamId)
Http3GoAwayStreamId(_http3Logger, connectionId, goAwayStreamId);
}
+ [LoggerMessage(54, LogLevel.Debug, @"Connection id ""{ConnectionId}"" aborted since at least {Count} ENHANCE_YOUR_CALM responses were recorded per second.", EventName = "Http2TooManyEnhanceYourCalms")]
+ private static partial void Http2TooManyEnhanceYourCalms(ILogger logger, string connectionId, int count);
+
+ public void Http2TooManyEnhanceYourCalms(string connectionId, int count)
+ {
+ Http2TooManyEnhanceYourCalms(_http2Logger, connectionId, count);
+ }
+
+ [LoggerMessage(55, LogLevel.Debug, @"Connection id ""{ConnectionId}"" exceeded the output flow control maximum queue size of {Count}.", EventName = "Http2FlowControlQueueOperationsExceeded")]
+ private static partial void Http2FlowControlQueueOperationsExceeded(ILogger logger, string connectionId, int count);
+
+ public void Http2FlowControlQueueOperationsExceeded(string connectionId, int count)
+ {
+ Http2FlowControlQueueOperationsExceeded(_http3Logger, connectionId, count);
+ }
+
+ [LoggerMessage(56, LogLevel.Debug, @"Connection id ""{ConnectionId}"" configured maximum flow control queue size {Actual} is less than double the maximum streams per connection {Expected}. Increasing configured value to {Expected}.", EventName = "Http2FlowControlQueueMaximumTooLow")]
+ private static partial void Http2FlowControlQueueMaximumTooLow(ILogger logger, string connectionId, int expected, int actual);
+
+ public void Http2FlowControlQueueMaximumTooLow(string connectionId, int expected, int actual)
+ {
+ Http2FlowControlQueueMaximumTooLow(_http3Logger, connectionId, expected, actual);
+ }
+
public virtual void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter)
=> _generalLogger.Log(logLevel, eventId, state, exception, formatter);
diff --git a/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTrace.cs b/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTrace.cs
index 7a87e2ee4041..c9052461b9ed 100644
--- a/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTrace.cs
+++ b/src/Servers/Kestrel/perf/Microbenchmarks/Mocks/MockTrace.cs
@@ -59,7 +59,10 @@ public void Http2ConnectionClosing(string connectionId) { }
public void Http2ConnectionClosed(string connectionId, int highestOpenedStreamId) { }
public void Http2FrameReceived(string connectionId, Http2Frame frame) { }
public void Http2FrameSending(string connectionId, Http2Frame frame) { }
+ public void Http2TooManyEnhanceYourCalms(string connectionId, int count) { }
public void Http2MaxConcurrentStreamsReached(string connectionId) { }
+ public void Http2FlowControlQueueOperationsExceeded(string connectionId, int count) { }
+ public void Http2FlowControlQueueMaximumTooLow(string connectionId, int expected, int actual) { }
public void InvalidResponseHeaderRemoved() { }
public void Http3ConnectionError(string connectionId, Http3ConnectionErrorException ex) { }
public void Http3ConnectionClosing(string connectionId) { }
diff --git a/src/Servers/Kestrel/samples/Http2SampleApp/Http2SampleApp.csproj b/src/Servers/Kestrel/samples/Http2SampleApp/Http2SampleApp.csproj
index cc404533ad8f..d0930e69fee0 100644
--- a/src/Servers/Kestrel/samples/Http2SampleApp/Http2SampleApp.csproj
+++ b/src/Servers/Kestrel/samples/Http2SampleApp/Http2SampleApp.csproj
@@ -1,4 +1,4 @@
-
+
$(DefaultNetCoreTargetFramework)
diff --git a/src/Servers/Kestrel/shared/test/CompositeKestrelTrace.cs b/src/Servers/Kestrel/shared/test/CompositeKestrelTrace.cs
index 523e77a059ac..1245abfd2a4b 100644
--- a/src/Servers/Kestrel/shared/test/CompositeKestrelTrace.cs
+++ b/src/Servers/Kestrel/shared/test/CompositeKestrelTrace.cs
@@ -234,12 +234,30 @@ public void Http2FrameSending(string connectionId, Http2Frame frame)
_trace2.Http2FrameSending(connectionId, frame);
}
+ public void Http2TooManyEnhanceYourCalms(string connectionId, int count)
+ {
+ _trace1.Http2TooManyEnhanceYourCalms(connectionId, count);
+ _trace2.Http2TooManyEnhanceYourCalms(connectionId, count);
+ }
+
public void Http2MaxConcurrentStreamsReached(string connectionId)
{
_trace1.Http2MaxConcurrentStreamsReached(connectionId);
_trace2.Http2MaxConcurrentStreamsReached(connectionId);
}
+ public void Http2FlowControlQueueOperationsExceeded(string connectionId, int count)
+ {
+ _trace1.Http2FlowControlQueueOperationsExceeded(connectionId, count);
+ _trace2.Http2FlowControlQueueOperationsExceeded(connectionId, count);
+ }
+
+ public void Http2FlowControlQueueMaximumTooLow(string connectionId, int expected, int actual)
+ {
+ _trace1.Http2FlowControlQueueMaximumTooLow(connectionId, expected, actual);
+ _trace2.Http2FlowControlQueueMaximumTooLow(connectionId, expected, actual);
+ }
+
public void InvalidResponseHeaderRemoved()
{
_trace1.InvalidResponseHeaderRemoved();