@@ -33,14 +33,51 @@ internal partial class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeade
3333 private  const  PseudoHeaderFields  _mandatoryRequestPseudoHeaderFields  = 
3434 PseudoHeaderFields . Method  |  PseudoHeaderFields . Path  |  PseudoHeaderFields . Scheme ; 
3535
36+  private  const  string  EnhanceYourCalmMaximumCountProperty  =  "Microsoft.AspNetCore.Server.Kestrel.Http2.EnhanceYourCalmCount" ; 
37+  private  const  string  MaximumFlowControlQueueSizeProperty  =  "Microsoft.AspNetCore.Server.Kestrel.Http2.MaxConnectionFlowControlQueueSize" ; 
38+ 
39+  private  static readonly  int  _enhanceYourCalmMaximumCount  =  AppContext . GetData ( EnhanceYourCalmMaximumCountProperty )  is  int  eycMaxCount 
40+  ?  eycMaxCount 
41+  :  10 ; 
42+ 
43+  // Accumulate _enhanceYourCalmCount over the course of EnhanceYourCalmTickWindowCount ticks. 
44+  // This should make bursts less likely to trigger disconnects. 
45+  private  const  int  EnhanceYourCalmTickWindowCount  =  5 ; 
46+ 
47+  private  static bool  IsEnhanceYourCalmEnabled  =>  _enhanceYourCalmMaximumCount  >  0 ; 
48+ 
49+  private  static readonly  int ?  ConfiguredMaximumFlowControlQueueSize  =  GetConfiguredMaximumFlowControlQueueSize ( ) ; 
50+ 
51+  private  static int ?  GetConfiguredMaximumFlowControlQueueSize ( ) 
52+  { 
53+  var  data  =  AppContext . GetData ( MaximumFlowControlQueueSizeProperty ) ; 
54+ 
55+  if  ( data  is  int  count ) 
56+  { 
57+  return  count ; 
58+  } 
59+ 
60+  if  ( data  is  string  countStr  &&  int . TryParse ( countStr ,  out  var  parsed ) ) 
61+  { 
62+  return  parsed ; 
63+  } 
64+ 
65+  return  null ; 
66+  } 
67+ 
68+  private  readonly  int  _maximumFlowControlQueueSize ; 
69+ 
70+  private  bool  IsMaximumFlowControlQueueSizeEnabled  =>  _maximumFlowControlQueueSize  >  0 ; 
71+ 
3672 private  readonly  HttpConnectionContext  _context ; 
3773 private  readonly  Http2FrameWriter  _frameWriter ; 
3874 private  readonly  Pipe  _input ; 
3975 private  readonly  Task  _inputTask ; 
4076 private  readonly  int  _minAllocBufferSize ; 
4177 private  readonly  HPackDecoder  _hpackDecoder ; 
4278 private  readonly  InputFlowControl  _inputFlowControl ; 
43-  private  readonly  OutputFlowControl  _outputFlowControl  =  new  OutputFlowControl ( new  MultipleAwaitableProvider ( ) ,  Http2PeerSettings . DefaultInitialWindowSize ) ; 
79+  private  readonly  OutputFlowControl  _outputFlowControl ; 
80+  private  readonly  AwaitableProvider  _outputFlowControlAwaitableProvider ;  // Keep our own reference so we can track queue size 
4481
4582 private  readonly  Http2PeerSettings  _serverSettings  =  new  Http2PeerSettings ( ) ; 
4683 private  readonly  Http2PeerSettings  _clientSettings  =  new  Http2PeerSettings ( ) ; 
@@ -59,6 +96,9 @@ internal partial class Http2Connection : IHttp2StreamLifetimeHandler, IHttpHeade
5996 private  int  _clientActiveStreamCount ; 
6097 private  int  _serverActiveStreamCount ; 
6198
99+  private  int  _enhanceYourCalmCount ; 
100+  private  int  _tickCount ; 
101+ 
62102 // The following are the only fields that can be modified outside of the ProcessRequestsAsync loop. 
63103 private  readonly  ConcurrentQueue < Http2Stream >  _completedStreams  =  new  ConcurrentQueue < Http2Stream > ( ) ; 
64104 private  readonly  StreamCloseAwaitable  _streamCompletionAwaitable  =  new  StreamCloseAwaitable ( ) ; 
@@ -88,6 +128,9 @@ public Http2Connection(HttpConnectionContext context)
88128 // Capture the ExecutionContext before dispatching HTTP/2 middleware. Will be restored by streams when processing request 
89129 _context . InitialExecutionContext  =  ExecutionContext . Capture ( ) ; 
90130
131+  _outputFlowControlAwaitableProvider  =  new  MultipleAwaitableProvider ( ) ; 
132+  _outputFlowControl  =  new  OutputFlowControl ( _outputFlowControlAwaitableProvider ,  Http2PeerSettings . DefaultInitialWindowSize ) ; 
133+ 
91134 _frameWriter  =  new  Http2FrameWriter ( 
92135 context . Transport . Output , 
93136 context . ConnectionContext , 
@@ -129,6 +172,16 @@ public Http2Connection(HttpConnectionContext context)
129172 _serverSettings . MaxHeaderListSize  =  ( uint ) httpLimits . MaxRequestHeadersTotalSize ; 
130173 _serverSettings . InitialWindowSize  =  ( uint ) http2Limits . InitialStreamWindowSize ; 
131174
175+  _maximumFlowControlQueueSize  =  ConfiguredMaximumFlowControlQueueSize  is  null 
176+  ?  4  *  http2Limits . MaxStreamsPerConnection 
177+  :  ( int ) ConfiguredMaximumFlowControlQueueSize ; 
178+ 
179+  if  ( _maximumFlowControlQueueSize  <  http2Limits . MaxStreamsPerConnection ) 
180+  { 
181+  _maximumFlowControlQueueSize  =  http2Limits . MaxStreamsPerConnection ; 
182+  Log . LogTrace ( $ "The configured maximum flow control queue size { ConfiguredMaximumFlowControlQueueSize }  is less than the maximum streams per connection { http2Limits . MaxStreamsPerConnection }  - increasing to match.") ; 
183+  } 
184+ 
132185 // Start pool off at a smaller size if the max number of streams is less than the InitialStreamPoolSize 
133186 StreamPool  =  new  PooledStreamStack < Http2Stream > ( Math . Min ( InitialStreamPoolSize ,  http2Limits . MaxStreamsPerConnection ) ) ; 
134187
@@ -352,13 +405,20 @@ public async Task ProcessRequestsAsync<TContext>(IHttpApplication<TContext> appl
352405 stream . Abort ( new  IOException ( CoreStrings . Http2StreamAborted ,  connectionError ) ) ; 
353406 } 
354407
355-  // Use the server _serverActiveStreamCount to drain all requests on the server side. 
356-  // Can't use _clientActiveStreamCount now as we now decrement that count earlier/ 
357-  // Can't use _streams.Count as we wait for RST/END_STREAM before removing the stream from the dictionary 
358-  while  ( _serverActiveStreamCount  >  0 ) 
408+  // For some reason, this loop doesn't terminate when we're trying to abort. 
409+  // Since we're making a narrow fix for a patch, we'll bypass it in such scenarios. 
410+  // TODO: This is probably a bug - something in here should probably detect aborted 
411+  // connections and short-circuit. 
412+  if  ( ! ( IsEnhanceYourCalmEnabled  ||  IsMaximumFlowControlQueueSizeEnabled )  ||  error  is  not Http2ConnectionErrorException ) 
359413 { 
360-  await  _streamCompletionAwaitable ; 
361-  UpdateCompletedStreams ( ) ; 
414+  // Use the server _serverActiveStreamCount to drain all requests on the server side. 
415+  // Can't use _clientActiveStreamCount now as we now decrement that count earlier/ 
416+  // Can't use _streams.Count as we wait for RST/END_STREAM before removing the stream from the dictionary 
417+  while  ( _serverActiveStreamCount  >  0 ) 
418+  { 
419+  await  _streamCompletionAwaitable ; 
420+  UpdateCompletedStreams ( ) ; 
421+  } 
362422 } 
363423
364424 while  ( StreamPool . TryPop ( out  var  pooledStream ) ) 
@@ -1053,6 +1113,20 @@ private void StartStream()
10531113 throw  new  Http2StreamErrorException ( _currentHeadersStream . StreamId ,  CoreStrings . Http2ErrorMaxStreams ,  Http2ErrorCode . REFUSED_STREAM ) ; 
10541114 } 
10551115
1116+  if  ( IsMaximumFlowControlQueueSizeEnabled  &&  _outputFlowControlAwaitableProvider . ActiveCount  >  _maximumFlowControlQueueSize ) 
1117+  { 
1118+  Log . Http2FlowControlQueueOperationsExceeded ( _context . ConnectionId ,  _maximumFlowControlQueueSize ) ; 
1119+ 
1120+  // Now that we've logged a useful message, we can put vague text in the exception 
1121+  // messages in case they somehow make it back to the client (not expected) 
1122+ 
1123+  // This will close the socket - we want to do that right away 
1124+  Abort ( new  ConnectionAbortedException ( "HTTP/2 connection exceeded the outgoing flow control maximum queue size." ) ) ; 
1125+ 
1126+  // Throwing an exception as well will help us clean up on our end more quickly by (e.g.) skipping processing of already-buffered input 
1127+  throw  new  Http2ConnectionErrorException ( CoreStrings . Http2ConnectionFaulted ,  Http2ErrorCode . INTERNAL_ERROR ) ; 
1128+  } 
1129+ 
10561130 // We don't use the _serverActiveRequestCount here as during shutdown, it and the dictionary counts get out of sync. 
10571131 // The streams still exist in the dictionary until the client responds with a RST or END_STREAM. 
10581132 // Also, we care about the dictionary size for too much memory consumption. 
@@ -1061,6 +1135,20 @@ private void StartStream()
10611135 // Server is getting hit hard with connection resets. 
10621136 // Tell client to calm down. 
10631137 // TODO consider making when to send ENHANCE_YOUR_CALM configurable? 
1138+ 
1139+  if  ( IsEnhanceYourCalmEnabled  &&  Interlocked . Increment ( ref  _enhanceYourCalmCount )  >  EnhanceYourCalmTickWindowCount  *  _enhanceYourCalmMaximumCount ) 
1140+  { 
1141+  Log . Http2TooManyEnhanceYourCalms ( _context . ConnectionId ,  _enhanceYourCalmMaximumCount ) ; 
1142+ 
1143+  // Now that we've logged a useful message, we can put vague text in the exception 
1144+  // messages in case they somehow make it back to the client (not expected) 
1145+ 
1146+  // This will close the socket - we want to do that right away 
1147+  Abort ( new  ConnectionAbortedException ( CoreStrings . Http2ConnectionFaulted ) ) ; 
1148+  // Throwing an exception as well will help us clean up on our end more quickly by (e.g.) skipping processing of already-buffered input 
1149+  throw  new  Http2ConnectionErrorException ( CoreStrings . Http2ConnectionFaulted ,  Http2ErrorCode . ENHANCE_YOUR_CALM ) ; 
1150+  } 
1151+ 
10641152 throw  new  Http2StreamErrorException ( _currentHeadersStream . StreamId ,  CoreStrings . Http2TellClientToCalmDown ,  Http2ErrorCode . ENHANCE_YOUR_CALM ) ; 
10651153 } 
10661154 } 
@@ -1123,6 +1211,10 @@ private void AbortStream(int streamId, IOException error)
11231211 void  IRequestProcessor . Tick ( DateTimeOffset  now ) 
11241212 { 
11251213 Input . CancelPendingRead ( ) ; 
1214+  if  ( IsEnhanceYourCalmEnabled  &&  ++ _tickCount  %  EnhanceYourCalmTickWindowCount  ==  0 ) 
1215+  { 
1216+  Interlocked . Exchange ( ref  _enhanceYourCalmCount ,  0 ) ; 
1217+  } 
11261218 } 
11271219
11281220 void  IHttp2StreamLifetimeHandler . OnStreamCompleted ( Http2Stream  stream ) 
0 commit comments