Skip to content

Commit 6c95ad9

Browse files
Fix Abort Lock (#55738)
* Fix Abort lock * better * some updates * comment + rename --------- Co-authored-by: Brennan <[email protected]>
1 parent 8198eeb commit 6c95ad9

File tree

2 files changed

+62
-20
lines changed

2 files changed

+62
-20
lines changed

src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs

Lines changed: 38 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -379,19 +379,36 @@ public void UpdateMaxFrameSize(uint maxFrameSize)
379379
}
380380
}
381381

382+
/// <summary>
383+
/// Call while in the <see cref="_writeLock"/>.
384+
/// </summary>
385+
/// <returns><c>true</c> if already completed.</returns>
386+
private bool CompleteUnsynchronized()
387+
{
388+
if (_completed)
389+
{
390+
return true;
391+
}
392+
393+
_completed = true;
394+
_outputWriter.Abort();
395+
396+
return false;
397+
}
398+
382399
public void Complete()
383400
{
384401
lock (_writeLock)
385402
{
386-
if (_completed)
403+
if (CompleteUnsynchronized())
387404
{
388405
return;
389406
}
390-
391-
_completed = true;
392-
AbortConnectionFlowControl();
393-
_outputWriter.Abort();
394407
}
408+
409+
// Call outside of _writeLock as this can call Http2OutputProducer.Stop which can acquire Http2OutputProducer._dataWriterLock
410+
// which is not the desired lock order
411+
AbortConnectionFlowControl();
395412
}
396413

397414
public Task ShutdownAsync()
@@ -413,8 +430,15 @@ public void Abort(ConnectionAbortedException error)
413430
_aborted = true;
414431
_connectionContext.Abort(error);
415432

416-
Complete();
433+
if (CompleteUnsynchronized())
434+
{
435+
return;
436+
}
417437
}
438+
439+
// Call outside of _writeLock as this can call Http2OutputProducer.Stop which can acquire Http2OutputProducer._dataWriterLock
440+
// which is not the desired lock order
441+
AbortConnectionFlowControl();
418442
}
419443

420444
private ValueTask<FlushResult> FlushEndOfStreamHeadersAsync(Http2Stream stream)
@@ -487,7 +511,7 @@ private void WriteResponseHeadersUnsynchronized(int streamId, int statusCode, Ht
487511
_outgoingFrame.PrepareHeaders(headerFrameFlags, streamId);
488512
var buffer = _headerEncodingBuffer.AsSpan();
489513
var done = HPackHeaderWriter.BeginEncodeHeaders(statusCode, _hpackEncoder, _headersEnumerator, buffer, out var payloadLength);
490-
FinishWritingHeaders(streamId, payloadLength, done);
514+
FinishWritingHeadersUnsynchronized(streamId, payloadLength, done);
491515
}
492516
// Any exception from the HPack encoder can leave the dynamic table in a corrupt state.
493517
// Since we allow custom header encoders we don't know what type of exceptions to expect.
@@ -528,7 +552,7 @@ private ValueTask<FlushResult> WriteDataAndTrailersAsync(Http2Stream stream, in
528552
_outgoingFrame.PrepareHeaders(Http2HeadersFrameFlags.END_STREAM, streamId);
529553
var buffer = _headerEncodingBuffer.AsSpan();
530554
var done = HPackHeaderWriter.BeginEncodeHeaders(_hpackEncoder, _headersEnumerator, buffer, out var payloadLength);
531-
FinishWritingHeaders(streamId, payloadLength, done);
555+
FinishWritingHeadersUnsynchronized(streamId, payloadLength, done);
532556
}
533557
// Any exception from the HPack encoder can leave the dynamic table in a corrupt state.
534558
// Since we allow custom header encoders we don't know what type of exceptions to expect.
@@ -542,7 +566,7 @@ private ValueTask<FlushResult> WriteDataAndTrailersAsync(Http2Stream stream, in
542566
}
543567
}
544568

545-
private void FinishWritingHeaders(int streamId, int payloadLength, bool done)
569+
private void FinishWritingHeadersUnsynchronized(int streamId, int payloadLength, bool done)
546570
{
547571
var buffer = _headerEncodingBuffer.AsSpan();
548572
_outgoingFrame.PayloadLength = payloadLength;
@@ -934,6 +958,11 @@ private void ConsumeConnectionWindow(long bytes)
934958
}
935959
}
936960

961+
/// <summary>
962+
/// Do not call this method under the _writeLock.
963+
/// This method can call Http2OutputProducer.Stop which can acquire Http2OutputProducer._dataWriterLock
964+
/// which is not the desired lock order
965+
/// </summary>
937966
private void AbortConnectionFlowControl()
938967
{
939968
lock (_windowUpdateLock)

src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs

Lines changed: 24 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -590,6 +590,7 @@ public void Reset()
590590

591591
internal void OnRequestProcessingEnded()
592592
{
593+
var shouldCompleteStream = false;
593594
lock (_dataWriterLock)
594595
{
595596
if (_requestProcessingComplete)
@@ -600,15 +601,24 @@ internal void OnRequestProcessingEnded()
600601

601602
_requestProcessingComplete = true;
602603

603-
if (_completedResponse)
604-
{
605-
Stream.CompleteStream(errored: false);
606-
}
604+
shouldCompleteStream = _completedResponse;
605+
}
606+
607+
// Complete outside of lock, anything this method does that needs a lock will acquire a lock itself.
608+
// Additionally, this method should only be called once per Reset so calling outside of the lock is fine from the perspective
609+
// of multiple threads calling OnRequestProcessingEnded.
610+
if (shouldCompleteStream)
611+
{
612+
Stream.CompleteStream(errored: false);
607613
}
614+
608615
}
609616

610617
internal ValueTask<FlushResult> CompleteResponseAsync()
611618
{
619+
var shouldCompleteStream = false;
620+
ValueTask<FlushResult> task = default;
621+
612622
lock (_dataWriterLock)
613623
{
614624
if (_completedResponse)
@@ -619,22 +629,25 @@ internal ValueTask<FlushResult> CompleteResponseAsync()
619629

620630
_completedResponse = true;
621631

622-
ValueTask<FlushResult> task = default;
623-
624632
if (_resetErrorCode is { } error)
625633
{
626634
// If we have an error code to write, write it now that we're done with the response.
627635
// Always send the reset even if the response body is completed. The request body may not have completed yet.
628636
task = _frameWriter.WriteRstStreamAsync(StreamId, error);
629637
}
630638

631-
if (_requestProcessingComplete)
632-
{
633-
Stream.CompleteStream(errored: false);
634-
}
639+
shouldCompleteStream = _requestProcessingComplete;
640+
}
635641

636-
return task;
642+
// Complete outside of lock, anything this method does that needs a lock will acquire a lock itself.
643+
// CompleteResponseAsync also should never be called in parallel so calling this outside of the lock doesn't
644+
// cause any weirdness with parallel threads calling this method and no longer waiting on the stream completion call.
645+
if (shouldCompleteStream)
646+
{
647+
Stream.CompleteStream(errored: false);
637648
}
649+
650+
return task;
638651
}
639652

640653
internal Memory<byte> GetFakeMemory(int minSize)

0 commit comments

Comments
 (0)