Don't take SocketOutput's _contextLock to return written blocks
This commit is contained in:
parent
7ee13875b1
commit
ce6e65b1d7
|
|
@ -378,6 +378,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
_lastWriteError = error;
|
_lastWriteError = error;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PoolWriteContext(writeContext);
|
||||||
|
|
||||||
// _numBytesPreCompleted can temporarily go negative in the event there are
|
// _numBytesPreCompleted can temporarily go negative in the event there are
|
||||||
// completed writes that we haven't triggered callbacks for yet.
|
// completed writes that we haven't triggered callbacks for yet.
|
||||||
_numBytesPreCompleted -= bytesWritten;
|
_numBytesPreCompleted -= bytesWritten;
|
||||||
|
|
@ -501,6 +503,16 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void PoolWriteContext(WriteContext writeContext)
|
||||||
|
{
|
||||||
|
// Called inside _contextLock
|
||||||
|
if (_writeContextPool.Count < _maxPooledWriteContexts)
|
||||||
|
{
|
||||||
|
writeContext.Reset();
|
||||||
|
_writeContextPool.Enqueue(writeContext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ISocketOutput.Write(ArraySegment<byte> buffer, bool chunk)
|
void ISocketOutput.Write(ArraySegment<byte> buffer, bool chunk)
|
||||||
{
|
{
|
||||||
WriteAsync(buffer, default(CancellationToken), chunk, isSync: true).GetAwaiter().GetResult();
|
WriteAsync(buffer, default(CancellationToken), chunk, isSync: true).GetAwaiter().GetResult();
|
||||||
|
|
@ -546,7 +558,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
private class WriteContext
|
private class WriteContext
|
||||||
{
|
{
|
||||||
private static readonly WaitCallback _returnWrittenBlocks = (state) => ((WriteContext)state).ReturnWrittenBlocks();
|
private static readonly WaitCallback _returnWrittenBlocks = (state) => ReturnWrittenBlocks((MemoryPoolBlock)state);
|
||||||
private static readonly WaitCallback _completeWrite = (state) => ((WriteContext)state).CompleteOnThreadPool();
|
private static readonly WaitCallback _completeWrite = (state) => ((WriteContext)state).CompleteOnThreadPool();
|
||||||
|
|
||||||
private SocketOutput Self;
|
private SocketOutput Self;
|
||||||
|
|
@ -555,11 +567,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
private MemoryPoolIterator _lockedEnd;
|
private MemoryPoolIterator _lockedEnd;
|
||||||
private int _bufferCount;
|
private int _bufferCount;
|
||||||
|
|
||||||
// _returnBlocksCompleted and _writeCompleted help determine when it's safe to pool the WriteContext
|
|
||||||
// These are both guarded by the _contextLock.
|
|
||||||
private bool _returnBlocksCompleted;
|
|
||||||
private bool _writeCompleted;
|
|
||||||
|
|
||||||
public int ByteCount;
|
public int ByteCount;
|
||||||
public bool SocketShutdownSend;
|
public bool SocketShutdownSend;
|
||||||
public bool SocketDisconnect;
|
public bool SocketDisconnect;
|
||||||
|
|
@ -656,8 +663,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Self.OnWriteCompleted(this);
|
Self.OnWriteCompleted(this);
|
||||||
_writeCompleted = true;
|
|
||||||
TryPool();
|
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
|
|
@ -677,8 +682,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Self.OnWriteCompleted(this);
|
Self.OnWriteCompleted(this);
|
||||||
_writeCompleted = true;
|
|
||||||
TryPool();
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|
@ -694,23 +697,11 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
|
|
||||||
private void ScheduleReturnWrittenBlocks()
|
private void ScheduleReturnWrittenBlocks()
|
||||||
{
|
{
|
||||||
Self._threadPool.UnsafeRun(_returnWrittenBlocks, this);
|
var returnAll = false;
|
||||||
}
|
|
||||||
|
|
||||||
private void ReturnWrittenBlocks()
|
|
||||||
{
|
|
||||||
var block = _lockedStart.Block;
|
|
||||||
while (block != _lockedEnd.Block)
|
|
||||||
{
|
|
||||||
var returnBlock = block;
|
|
||||||
block = block.Next;
|
|
||||||
|
|
||||||
returnBlock.Pool.Return(returnBlock);
|
|
||||||
}
|
|
||||||
|
|
||||||
lock (Self._returnLock)
|
lock (Self._returnLock)
|
||||||
{
|
{
|
||||||
// If everything has been fully written, return _tail.
|
// If everything has been fully written, return _tail/_lockedEnd.
|
||||||
if (_lockedEnd.Block == Self._tail &&
|
if (_lockedEnd.Block == Self._tail &&
|
||||||
_lockedEnd.Index == Self._tail.End &&
|
_lockedEnd.Index == Self._tail.End &&
|
||||||
Self._lastStart.IsDefault)
|
Self._lastStart.IsDefault)
|
||||||
|
|
@ -718,16 +709,43 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
Debug.Assert(Self._head == Self._tail);
|
Debug.Assert(Self._head == Self._tail);
|
||||||
Debug.Assert(Self._tail.Start == Self._tail.End);
|
Debug.Assert(Self._tail.Start == Self._tail.End);
|
||||||
|
|
||||||
_lockedEnd.Block.Pool.Return(_lockedEnd.Block);
|
|
||||||
Self._head = null;
|
Self._head = null;
|
||||||
Self._tail = null;
|
Self._tail = null;
|
||||||
|
returnAll = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lock (Self._contextLock)
|
if (!returnAll)
|
||||||
{
|
{
|
||||||
_returnBlocksCompleted = true;
|
var block = _lockedStart.Block;
|
||||||
TryPool();
|
var end = _lockedEnd.Block;
|
||||||
|
if (block == end)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
while (block.Next != end)
|
||||||
|
{
|
||||||
|
block = block.Next;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set the Next pointer in the block before _lockedEnd.Block to null.
|
||||||
|
// This prevents _lockedEnd.Block from being returned if it isn't fully
|
||||||
|
// written, or it's still being written to.
|
||||||
|
block.Next = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
Self._threadPool.UnsafeRun(_returnWrittenBlocks, _lockedStart.Block);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReturnWrittenBlocks(MemoryPoolBlock block)
|
||||||
|
{
|
||||||
|
while (block != null)
|
||||||
|
{
|
||||||
|
var returnBlock = block;
|
||||||
|
block = block.Next;
|
||||||
|
|
||||||
|
returnBlock.Pool.Return(returnBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -749,26 +767,12 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Internal.Http
|
||||||
BytesBetween(_lockedStart, _lockedEnd, out ByteCount, out _bufferCount);
|
BytesBetween(_lockedStart, _lockedEnd, out ByteCount, out _bufferCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void TryPool()
|
public void Reset()
|
||||||
{
|
|
||||||
// Called inside _contextLock
|
|
||||||
if (_writeCompleted &&
|
|
||||||
_returnBlocksCompleted &&
|
|
||||||
Self._writeContextPool.Count < _maxPooledWriteContexts)
|
|
||||||
{
|
|
||||||
Reset();
|
|
||||||
Self._writeContextPool.Enqueue(this);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private void Reset()
|
|
||||||
{
|
{
|
||||||
_lockedStart = default(MemoryPoolIterator);
|
_lockedStart = default(MemoryPoolIterator);
|
||||||
_lockedEnd = default(MemoryPoolIterator);
|
_lockedEnd = default(MemoryPoolIterator);
|
||||||
_bufferCount = 0;
|
_bufferCount = 0;
|
||||||
ByteCount = 0;
|
ByteCount = 0;
|
||||||
_writeCompleted = false;
|
|
||||||
_returnBlocksCompleted = false;
|
|
||||||
|
|
||||||
SocketShutdownSend = false;
|
SocketShutdownSend = false;
|
||||||
SocketDisconnect = false;
|
SocketDisconnect = false;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue