Use a single lock for writing to ConcurrentPipeWriter (#12285)
This commit is contained in:
parent
cae31a55f9
commit
7bed5df4d0
|
|
@ -83,7 +83,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
|
||||||
MemoryPool<byte> memoryPool)
|
MemoryPool<byte> memoryPool)
|
||||||
{
|
{
|
||||||
// Allow appending more data to the PipeWriter when a flush is pending.
|
// 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;
|
_connectionId = connectionId;
|
||||||
_connectionContext = connectionContext;
|
_connectionContext = connectionContext;
|
||||||
_log = log;
|
_log = log;
|
||||||
|
|
|
||||||
|
|
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
IKestrelTrace log)
|
IKestrelTrace log)
|
||||||
{
|
{
|
||||||
// Allow appending more data to the PipeWriter when a flush is pending.
|
// 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;
|
_connectionContext = connectionContext;
|
||||||
_http2Connection = http2Connection;
|
_http2Connection = http2Connection;
|
||||||
_connectionOutputFlowControl = connectionOutputFlowControl;
|
_connectionOutputFlowControl = connectionOutputFlowControl;
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http2
|
||||||
|
|
||||||
var pipe = CreateDataPipe(pool);
|
var pipe = CreateDataPipe(pool);
|
||||||
|
|
||||||
_pipeWriter = new ConcurrentPipeWriter(pipe.Writer, pool);
|
_pipeWriter = new ConcurrentPipeWriter(pipe.Writer, pool, _dataWriterLock);
|
||||||
_pipeReader = pipe.Reader;
|
_pipeReader = pipe.Reader;
|
||||||
|
|
||||||
// No need to pass in timeoutControl here, since no minDataRates are passed to the TimingPipeFlusher.
|
// No need to pass in timeoutControl here, since no minDataRates are passed to the TimingPipeFlusher.
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeW
|
||||||
|
|
||||||
private static readonly Exception _successfullyCompletedSentinel = new Exception();
|
private static readonly Exception _successfullyCompletedSentinel = new Exception();
|
||||||
|
|
||||||
private readonly object _sync = new object();
|
private readonly object _sync;
|
||||||
private readonly PipeWriter _innerPipeWriter;
|
private readonly PipeWriter _innerPipeWriter;
|
||||||
private readonly MemoryPool<byte> _pool;
|
private readonly MemoryPool<byte> _pool;
|
||||||
private readonly BufferSegmentStack _bufferSegmentPool = new BufferSegmentStack(InitialSegmentPoolSize);
|
private readonly BufferSegmentStack _bufferSegmentPool = new BufferSegmentStack(InitialSegmentPoolSize);
|
||||||
|
|
@ -51,97 +51,86 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure.PipeW
|
||||||
private bool _aborted;
|
private bool _aborted;
|
||||||
private Exception _completeException;
|
private Exception _completeException;
|
||||||
|
|
||||||
public ConcurrentPipeWriter(PipeWriter innerPipeWriter, MemoryPool<byte> pool)
|
public ConcurrentPipeWriter(PipeWriter innerPipeWriter, MemoryPool<byte> pool, object sync)
|
||||||
{
|
{
|
||||||
_innerPipeWriter = innerPipeWriter;
|
_innerPipeWriter = innerPipeWriter;
|
||||||
_pool = pool;
|
_pool = pool;
|
||||||
|
_sync = sync;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Memory<byte> GetMemory(int sizeHint = 0)
|
public override Memory<byte> GetMemory(int sizeHint = 0)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
if (_currentFlushTcs == null && _head == null)
|
||||||
{
|
{
|
||||||
if (_currentFlushTcs == null && _head == null)
|
return _innerPipeWriter.GetMemory(sizeHint);
|
||||||
{
|
|
||||||
return _innerPipeWriter.GetMemory(sizeHint);
|
|
||||||
}
|
|
||||||
|
|
||||||
AllocateMemoryUnsynchronized(sizeHint);
|
|
||||||
return _tailMemory;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AllocateMemoryUnsynchronized(sizeHint);
|
||||||
|
return _tailMemory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override Span<byte> GetSpan(int sizeHint = 0)
|
public override Span<byte> GetSpan(int sizeHint = 0)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
if (_currentFlushTcs == null && _head == null)
|
||||||
{
|
{
|
||||||
if (_currentFlushTcs == null && _head == null)
|
return _innerPipeWriter.GetSpan(sizeHint);
|
||||||
{
|
|
||||||
return _innerPipeWriter.GetSpan(sizeHint);
|
|
||||||
}
|
|
||||||
|
|
||||||
AllocateMemoryUnsynchronized(sizeHint);
|
|
||||||
return _tailMemory.Span;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AllocateMemoryUnsynchronized(sizeHint);
|
||||||
|
return _tailMemory.Span;
|
||||||
}
|
}
|
||||||
|
|
||||||
public override void Advance(int bytes)
|
public override void Advance(int bytes)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
if (_currentFlushTcs == null && _head == null)
|
||||||
{
|
{
|
||||||
if (_currentFlushTcs == null && _head == null)
|
_innerPipeWriter.Advance(bytes);
|
||||||
{
|
return;
|
||||||
_innerPipeWriter.Advance(bytes);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if ((uint)bytes > (uint)_tailMemory.Length)
|
|
||||||
{
|
|
||||||
ThrowArgumentOutOfRangeException(nameof(bytes));
|
|
||||||
}
|
|
||||||
|
|
||||||
_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)
|
public override ValueTask<FlushResult> FlushAsync(CancellationToken cancellationToken = default)
|
||||||
{
|
{
|
||||||
lock (_sync)
|
if (_currentFlushTcs != null)
|
||||||
|
{
|
||||||
|
return new ValueTask<FlushResult>(_currentFlushTcs.Task);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (_bytesBuffered > 0)
|
||||||
|
{
|
||||||
|
CopyAndReturnSegmentsUnsynchronized();
|
||||||
|
}
|
||||||
|
|
||||||
|
var flushTask = _innerPipeWriter.FlushAsync(cancellationToken);
|
||||||
|
|
||||||
|
if (flushTask.IsCompletedSuccessfully)
|
||||||
{
|
{
|
||||||
if (_currentFlushTcs != null)
|
if (_currentFlushTcs != null)
|
||||||
{
|
{
|
||||||
return new ValueTask<FlushResult>(_currentFlushTcs.Task);
|
CompleteFlushUnsynchronized(flushTask.GetAwaiter().GetResult(), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_bytesBuffered > 0)
|
return flushTask;
|
||||||
{
|
|
||||||
CopyAndReturnSegmentsUnsynchronized();
|
|
||||||
}
|
|
||||||
|
|
||||||
var flushTask = _innerPipeWriter.FlushAsync(cancellationToken);
|
|
||||||
|
|
||||||
if (flushTask.IsCompletedSuccessfully)
|
|
||||||
{
|
|
||||||
if (_currentFlushTcs != null)
|
|
||||||
{
|
|
||||||
CompleteFlushUnsynchronized(flushTask.GetAwaiter().GetResult(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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)
|
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)
|
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.
|
||||||
|
|
||||||
|
// 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)
|
||||||
{
|
{
|
||||||
// We store the complete exception or s sentinel exception instance in a field if a flush was ongoing.
|
if (_bytesBuffered > 0)
|
||||||
// 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;
|
|
||||||
|
|
||||||
if (_currentFlushTcs == null)
|
|
||||||
{
|
{
|
||||||
if (_bytesBuffered > 0)
|
CopyAndReturnSegmentsUnsynchronized();
|
||||||
{
|
|
||||||
CopyAndReturnSegmentsUnsynchronized();
|
|
||||||
}
|
|
||||||
|
|
||||||
CleanupSegmentsUnsynchronized();
|
|
||||||
|
|
||||||
_innerPipeWriter.Complete(exception);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CleanupSegmentsUnsynchronized();
|
||||||
|
|
||||||
|
_innerPipeWriter.Complete(exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Abort()
|
public void Abort()
|
||||||
{
|
{
|
||||||
lock (_sync)
|
_aborted = true;
|
||||||
{
|
|
||||||
_aborted = true;
|
|
||||||
|
|
||||||
// If we're flushing, the cleanup will happen after the flush.
|
// If we're flushing, the cleanup will happen after the flush.
|
||||||
if (_currentFlushTcs == null)
|
if (_currentFlushTcs == null)
|
||||||
{
|
{
|
||||||
CleanupSegmentsUnsynchronized();
|
CleanupSegmentsUnsynchronized();
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
};
|
};
|
||||||
|
|
||||||
var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray);
|
var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray);
|
||||||
var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool);
|
var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool, new object());
|
||||||
|
|
||||||
var memory = concurrentPipeWriter.GetMemory();
|
var memory = concurrentPipeWriter.GetMemory();
|
||||||
Assert.Equal(1, mockPipeWriter.GetMemoryCallCount);
|
Assert.Equal(1, mockPipeWriter.GetMemoryCallCount);
|
||||||
|
|
@ -72,7 +72,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
};
|
};
|
||||||
|
|
||||||
var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray);
|
var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray);
|
||||||
var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool);
|
var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool, new object());
|
||||||
|
|
||||||
var memory = concurrentPipeWriter.GetMemory();
|
var memory = concurrentPipeWriter.GetMemory();
|
||||||
Assert.Equal(1, mockPipeWriter.GetMemoryCallCount);
|
Assert.Equal(1, mockPipeWriter.GetMemoryCallCount);
|
||||||
|
|
@ -152,7 +152,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
};
|
};
|
||||||
|
|
||||||
var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray);
|
var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray);
|
||||||
var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool);
|
var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool, new object());
|
||||||
|
|
||||||
var memory = concurrentPipeWriter.GetMemory();
|
var memory = concurrentPipeWriter.GetMemory();
|
||||||
Assert.Equal(1, mockPipeWriter.GetMemoryCallCount);
|
Assert.Equal(1, mockPipeWriter.GetMemoryCallCount);
|
||||||
|
|
@ -218,7 +218,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
};
|
};
|
||||||
|
|
||||||
var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray);
|
var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray);
|
||||||
var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool);
|
var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool, new object());
|
||||||
|
|
||||||
var memory = concurrentPipeWriter.GetMemory();
|
var memory = concurrentPipeWriter.GetMemory();
|
||||||
Assert.Equal(1, mockPipeWriter.GetMemoryCallCount);
|
Assert.Equal(1, mockPipeWriter.GetMemoryCallCount);
|
||||||
|
|
@ -273,7 +273,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
|
||||||
};
|
};
|
||||||
|
|
||||||
var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray);
|
var mockPipeWriter = new MockPipeWriter(pipeWriterFlushTcsArray);
|
||||||
var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool);
|
var concurrentPipeWriter = new ConcurrentPipeWriter(mockPipeWriter, diagnosticPool, new object());
|
||||||
|
|
||||||
var memory = concurrentPipeWriter.GetMemory();
|
var memory = concurrentPipeWriter.GetMemory();
|
||||||
Assert.Equal(1, mockPipeWriter.GetMemoryCallCount);
|
Assert.Equal(1, mockPipeWriter.GetMemoryCallCount);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue