@@ -370,19 +370,36 @@ public void UpdateMaxFrameSize(uint maxFrameSize)
370370 }
371371 }
372372
373+ /// <summary>
374+ /// Call while in the <see cref="_writeLock"/>.
375+ /// </summary>
376+ /// <returns><c>true</c> if already completed.</returns>
377+ private bool CompleteUnsynchronized ( )
378+ {
379+ if ( _completed )
380+ {
381+ return true ;
382+ }
383+
384+ _completed = true ;
385+ _outputWriter . Abort ( ) ;
386+
387+ return false ;
388+ }
389+
373390 public void Complete ( )
374391 {
375392 lock ( _writeLock )
376393 {
377- if ( _completed )
394+ if ( CompleteUnsynchronized ( ) )
378395 {
379396 return ;
380397 }
381-
382- _completed = true ;
383- AbortConnectionFlowControl ( ) ;
384- _outputWriter . Abort ( ) ;
385398 }
399+
400+ // Call outside of _writeLock as this can call Http2OutputProducer.Stop which can acquire Http2OutputProducer._dataWriterLock
401+ // which is not the desired lock order
402+ AbortConnectionFlowControl ( ) ;
386403 }
387404
388405 public Task ShutdownAsync ( )
@@ -404,8 +421,15 @@ public void Abort(ConnectionAbortedException error)
404421 _aborted = true ;
405422 _connectionContext . Abort ( error ) ;
406423
407- Complete ( ) ;
424+ if ( CompleteUnsynchronized ( ) )
425+ {
426+ return ;
427+ }
408428 }
429+
430+ // Call outside of _writeLock as this can call Http2OutputProducer.Stop which can acquire Http2OutputProducer._dataWriterLock
431+ // which is not the desired lock order
432+ AbortConnectionFlowControl ( ) ;
409433 }
410434
411435 private ValueTask < FlushResult > FlushEndOfStreamHeadersAsync ( Http2Stream stream )
@@ -478,7 +502,7 @@ private void WriteResponseHeadersUnsynchronized(int streamId, int statusCode, Ht
478502 _outgoingFrame . PrepareHeaders ( headerFrameFlags , streamId ) ;
479503 var buffer = _headerEncodingBuffer . AsSpan ( ) ;
480504 var done = HPackHeaderWriter . BeginEncodeHeaders ( statusCode , _hpackEncoder , _headersEnumerator , buffer , out var payloadLength ) ;
481- FinishWritingHeaders ( streamId , payloadLength , done ) ;
505+ FinishWritingHeadersUnsynchronized ( streamId , payloadLength , done ) ;
482506 }
483507 // Any exception from the HPack encoder can leave the dynamic table in a corrupt state.
484508 // Since we allow custom header encoders we don't know what type of exceptions to expect.
@@ -519,7 +543,7 @@ private ValueTask<FlushResult> WriteDataAndTrailersAsync(Http2Stream stream, in
519543 _outgoingFrame . PrepareHeaders ( Http2HeadersFrameFlags . END_STREAM , streamId ) ;
520544 var buffer = _headerEncodingBuffer . AsSpan ( ) ;
521545 var done = HPackHeaderWriter . BeginEncodeHeaders ( _hpackEncoder , _headersEnumerator , buffer , out var payloadLength ) ;
522- FinishWritingHeaders ( streamId , payloadLength , done ) ;
546+ FinishWritingHeadersUnsynchronized ( streamId , payloadLength , done ) ;
523547 }
524548 // Any exception from the HPack encoder can leave the dynamic table in a corrupt state.
525549 // Since we allow custom header encoders we don't know what type of exceptions to expect.
@@ -533,7 +557,7 @@ private ValueTask<FlushResult> WriteDataAndTrailersAsync(Http2Stream stream, in
533557 }
534558 }
535559
536- private void FinishWritingHeaders ( int streamId , int payloadLength , bool done )
560+ private void FinishWritingHeadersUnsynchronized ( int streamId , int payloadLength , bool done )
537561 {
538562 var buffer = _headerEncodingBuffer . AsSpan ( ) ;
539563 _outgoingFrame . PayloadLength = payloadLength ;
@@ -925,6 +949,11 @@ private void ConsumeConnectionWindow(long bytes)
925949 }
926950 }
927951
952+ /// <summary>
953+ /// Do not call this method under the _writeLock.
954+ /// This method can call Http2OutputProducer.Stop which can acquire Http2OutputProducer._dataWriterLock
955+ /// which is not the desired lock order
956+ /// </summary>
928957 private void AbortConnectionFlowControl ( )
929958 {
930959 lock ( _windowUpdateLock )
0 commit comments