diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs index 85a4da5fbe3e44b2e8c55eaf97ff7d6119437a39..374abf7adf8bd56de03cb3ad1c34907cd367fce3 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/Http1OutputProducer.cs @@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http MemoryPool<byte> memoryPool) { // Allow appending more data to the PipeWriter when a flush is pending. - _pipeWriter = new ConcurrentPipeWriter(pipeWriter, memoryPool); + _pipeWriter = new ConcurrentPipeWriter(pipeWriter, memoryPool, _contextLock); _connectionId = connectionId; _connectionContext = connectionContext; _log = log; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs index 1f2b0573927d9b43572415fb5a65ca04d3301331..294c524272415e2d4cb51504910cfe7fe48e8a73 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2FrameWriter.cs @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 IKestrelTrace log) { // Allow appending more data to the PipeWriter when a flush is pending. - _outputWriter = new ConcurrentPipeWriter(outputPipeWriter, memoryPool); + _outputWriter = new ConcurrentPipeWriter(outputPipeWriter, memoryPool, _writeLock); _connectionContext = connectionContext; _http2Connection = http2Connection; _connectionOutputFlowControl = connectionOutputFlowControl; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs index 46b1143929d79d4fa0585ecff6265af38d613337..6726f1fed674630bce4529bbd0296868e7eb301c 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http2/Http2OutputProducer.cs @@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2 var pipe = CreateDataPipe(pool); - _pipeWriter = new ConcurrentPipeWriter(pipe.Writer, pool); + _pipeWriter = new ConcurrentPipeWriter(pipe.Writer, pool, _dataWriterLock); _pipeReader = pipe.Reader; // No need to pass in timeoutControl here, since no minDataRates are passed to the TimingPipeFlusher. diff --git a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs index 5a35bafb8505147de9d7d0f605b4efe499b06124..c7e894253b2d3ddb7a98ad18f4a7a1f4beac1b73 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Infrastructure/PipeWriterHelpers/ConcurrentPipeWriter.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeW private static readonly Exception _successfullyCompletedSentinel = new Exception(); - private readonly object _sync = new object(); + private readonly object _sync; private readonly PipeWriter _innerPipeWriter; private readonly MemoryPool<byte> _pool; private readonly BufferSegmentStack _bufferSegmentPool = new BufferSegmentStack(InitialSegmentPoolSize); @@ -51,97 +51,86 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeW private bool _aborted; private Exception _completeException; - public ConcurrentPipeWriter(PipeWriter innerPipeWriter, MemoryPool<byte> pool) + public ConcurrentPipeWriter(PipeWriter innerPipeWriter, MemoryPool<byte> pool, object sync) { _innerPipeWriter = innerPipeWriter; _pool = pool; + _sync = sync; } public override Memory<byte> GetMemory(int sizeHint = 0) { - lock (_sync) + if (_currentFlushTcs == null && _head == null) { - if (_currentFlushTcs == null && _head == null) - { - return _innerPipeWriter.GetMemory(sizeHint); - } - - AllocateMemoryUnsynchronized(sizeHint); - return _tailMemory; + return _innerPipeWriter.GetMemory(sizeHint); } + + AllocateMemoryUnsynchronized(sizeHint); + return _tailMemory; } public override Span<byte> GetSpan(int sizeHint = 0) { - lock (_sync) + if (_currentFlushTcs == null && _head == null) { - if (_currentFlushTcs == null && _head == null) - { - return _innerPipeWriter.GetSpan(sizeHint); - } - - AllocateMemoryUnsynchronized(sizeHint); - return _tailMemory.Span; + return _innerPipeWriter.GetSpan(sizeHint); } + + AllocateMemoryUnsynchronized(sizeHint); + return _tailMemory.Span; } public override void Advance(int bytes) { - lock (_sync) + if (_currentFlushTcs == null && _head == null) { - if (_currentFlushTcs == null && _head == null) - { - _innerPipeWriter.Advance(bytes); - return; - } - - if ((uint)bytes > (uint)_tailMemory.Length) - { - ThrowArgumentOutOfRangeException(nameof(bytes)); - } + _innerPipeWriter.Advance(bytes); + return; + } - _tailBytesBuffered += bytes; - _bytesBuffered += bytes; - _tailMemory = _tailMemory.Slice(bytes); - _bufferedWritePending = false; + if ((uint)bytes > (uint)_tailMemory.Length) + { + ThrowArgumentOutOfRangeException(nameof(bytes)); } + + _tailBytesBuffered += bytes; + _bytesBuffered += bytes; + _tailMemory = _tailMemory.Slice(bytes); + _bufferedWritePending = false; } public override ValueTask<FlushResult> FlushAsync(CancellationToken cancellationToken = default) { - lock (_sync) + if (_currentFlushTcs != null) { - if (_currentFlushTcs != null) - { - return new ValueTask<FlushResult>(_currentFlushTcs.Task); - } + return new ValueTask<FlushResult>(_currentFlushTcs.Task); + } - if (_bytesBuffered > 0) - { - CopyAndReturnSegmentsUnsynchronized(); - } + if (_bytesBuffered > 0) + { + CopyAndReturnSegmentsUnsynchronized(); + } - var flushTask = _innerPipeWriter.FlushAsync(cancellationToken); + var flushTask = _innerPipeWriter.FlushAsync(cancellationToken); - if (flushTask.IsCompletedSuccessfully) + if (flushTask.IsCompletedSuccessfully) + { + if (_currentFlushTcs != null) { - if (_currentFlushTcs != null) - { - CompleteFlushUnsynchronized(flushTask.GetAwaiter().GetResult(), null); - } - - return flushTask; + CompleteFlushUnsynchronized(flushTask.GetAwaiter().GetResult(), null); } - // Use a TCS instead of something resettable so it can be awaited by multiple awaiters. - _currentFlushTcs = new TaskCompletionSource<FlushResult>(TaskCreationOptions.RunContinuationsAsynchronously); - var result = new ValueTask<FlushResult>(_currentFlushTcs.Task); - - // FlushAsyncAwaited clears the TCS prior to completing. Make sure to construct the ValueTask - // from the TCS before calling FlushAsyncAwaited in case FlushAsyncAwaited completes inline. - _ = FlushAsyncAwaited(flushTask, cancellationToken); - return result; + return flushTask; } + + // Use a TCS instead of something resettable so it can be awaited by multiple awaiters. + _currentFlushTcs = new TaskCompletionSource<FlushResult>(TaskCreationOptions.RunContinuationsAsynchronously); + var result = new ValueTask<FlushResult>(_currentFlushTcs.Task); + + // FlushAsyncAwaited clears the TCS prior to completing. Make sure to construct the ValueTask + // from the TCS before calling FlushAsyncAwaited in case FlushAsyncAwaited completes inline. + _ = FlushAsyncAwaited(flushTask, cancellationToken); + return result; } private async Task FlushAsyncAwaited(ValueTask<FlushResult> flushTask, CancellationToken cancellationToken) @@ -199,40 +188,34 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeW public override void Complete(Exception exception = null) { - lock (_sync) - { - // We store the complete exception or s sentinel exception instance in a field if a flush was ongoing. - // We call the inner Complete() method after the flush loop ended. + // We store the complete exception or s sentinel exception instance in a field if a flush was ongoing. + // We call the inner Complete() method after the flush loop ended. - // To simply ensure everything gets returned after the PipeWriter is left in some unknown state (say GetMemory() was - // called but not Advance(), or there's a flush pending), but you don't want to complete the inner pipe, just call Abort(). - _completeException = exception ?? _successfullyCompletedSentinel; + // To simply ensure everything gets returned after the PipeWriter is left in some unknown state (say GetMemory() was + // called but not Advance(), or there's a flush pending), but you don't want to complete the inner pipe, just call Abort(). + _completeException = exception ?? _successfullyCompletedSentinel; - if (_currentFlushTcs == null) + if (_currentFlushTcs == null) + { + if (_bytesBuffered > 0) { - if (_bytesBuffered > 0) - { - CopyAndReturnSegmentsUnsynchronized(); - } + CopyAndReturnSegmentsUnsynchronized(); + } - CleanupSegmentsUnsynchronized(); + CleanupSegmentsUnsynchronized(); - _innerPipeWriter.Complete(exception); - } + _innerPipeWriter.Complete(exception); } } public void Abort() { - lock (_sync) - { - _aborted = true; + _aborted = true; - // If we're flushing, the cleanup will happen after the flush. - if (_currentFlushTcs == null) - { - CleanupSegmentsUnsynchronized(); - } + // If we're flushing, the cleanup will happen after the flush. + if (_currentFlushTcs == null) + { + CleanupSegmentsUnsynchronized(); } } diff --git a/src/Servers/Kestrel/Core/test/ConcurrentPipeWriterTests.cs b/src/Servers/Kestrel/Core/test/ConcurrentPipeWriterTests.cs index eb3ab8eead9e4fe20bd98d78e5feefd9a93b3eec..bb3402bd739752ebd602cbd1f058810102fcf8f2 100644 --- a/src/Servers/Kestrel/Core/test/ConcurrentPipeWriterTests.cs +++ b/src/Servers/Kestrel/Core/test/ConcurrentPipeWriterTests.cs @@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests }; var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray); - var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool); + var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool, new object()); var memory = concurrentPipeWriter.GetMemory(); Assert.Equal(1, mockPipeWriter.GetMemoryCallCount); @@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests }; var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray); - var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool); + var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool, new object()); var memory = concurrentPipeWriter.GetMemory(); Assert.Equal(1, mockPipeWriter.GetMemoryCallCount); @@ -152,7 +152,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests }; var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray); - var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool); + var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool, new object()); var memory = concurrentPipeWriter.GetMemory(); Assert.Equal(1, mockPipeWriter.GetMemoryCallCount); @@ -218,7 +218,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests }; var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray); - var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool); + var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool, new object()); var memory = concurrentPipeWriter.GetMemory(); Assert.Equal(1, mockPipeWriter.GetMemoryCallCount); @@ -273,7 +273,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests }; var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray); - var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool); + var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool, new object()); var memory = concurrentPipeWriter.GetMemory(); Assert.Equal(1, mockPipeWriter.GetMemoryCallCount);