Skip to content

Commit 90f8928

Browse files
committed
Merged PR 38780: Fix Abort lock
Remove Http2OutputProducer.Stop from lock in Abort.
2 parents 1002bb7 + 146cf0a commit 90f8928

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

+38-9
Original file line numberDiff line numberDiff line change
@@ -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)

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

+24-11
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)