Use MemoryPool2 for SocketOutput Buffers

Add ProducingStart and ProducingComplete methods to ISocketOutput.
These new methods can help prevent double buffering when encoding.
This commit is contained in:
Stephen Halter 2015-11-20 01:56:46 -08:00
parent 9d852632c0
commit 2572256d3f
15 changed files with 408 additions and 76 deletions

View File

@ -22,7 +22,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Filter
IKestrelTrace logger) IKestrelTrace logger)
{ {
SocketInput = new SocketInput(memory); SocketInput = new SocketInput(memory);
SocketOutput = new StreamSocketOutput(filteredStream); SocketOutput = new StreamSocketOutput(filteredStream, memory);
_log = logger; _log = logger;
_filteredStream = filteredStream; _filteredStream = filteredStream;

View File

@ -13,10 +13,13 @@ namespace Microsoft.AspNet.Server.Kestrel.Filter
public class StreamSocketOutput : ISocketOutput public class StreamSocketOutput : ISocketOutput
{ {
private readonly Stream _outputStream; private readonly Stream _outputStream;
private readonly MemoryPool2 _memory;
private MemoryPoolBlock2 _producingBlock;
public StreamSocketOutput(Stream outputStream) public StreamSocketOutput(Stream outputStream, MemoryPool2 memory)
{ {
_outputStream = outputStream; _outputStream = outputStream;
_memory = memory;
} }
void ISocketOutput.Write(ArraySegment<byte> buffer, bool immediate) void ISocketOutput.Write(ArraySegment<byte> buffer, bool immediate)
@ -30,5 +33,27 @@ namespace Microsoft.AspNet.Server.Kestrel.Filter
_outputStream.Write(buffer.Array, buffer.Offset, buffer.Count); _outputStream.Write(buffer.Array, buffer.Offset, buffer.Count);
return TaskUtilities.CompletedTask; return TaskUtilities.CompletedTask;
} }
public MemoryPoolIterator2 ProducingStart()
{
_producingBlock = _memory.Lease();
return new MemoryPoolIterator2(_producingBlock);
}
public void ProducingComplete(MemoryPoolIterator2 end, int count)
{
var block = _producingBlock;
while (block != end.Block)
{
_outputStream.Write(block.Data.Array, block.Data.Offset, block.Data.Count);
var returnBlock = block;
block = block.Next;
returnBlock.Pool?.Return(returnBlock);
}
_outputStream.Write(end.Block.Array, end.Block.Data.Offset, end.Index);
end.Block.Pool?.Return(end.Block);
}
} }
} }

View File

@ -42,7 +42,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
_connectionId = Interlocked.Increment(ref _lastConnectionId); _connectionId = Interlocked.Increment(ref _lastConnectionId);
_rawSocketInput = new SocketInput(Memory2); _rawSocketInput = new SocketInput(Memory2);
_rawSocketOutput = new SocketOutput(Thread, _socket, this, _connectionId, Log); _rawSocketOutput = new SocketOutput(Thread, _socket, Memory2, this, _connectionId, Log);
} }
public void Start() public void Start()
@ -116,7 +116,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
// called from a libuv thread. // called from a libuv thread.
ThreadPool.QueueUserWorkItem(state => ThreadPool.QueueUserWorkItem(state =>
{ {
var connection = (Connection)this; var connection = (Connection)state;
connection._frame.Abort(); connection._frame.Abort();
}, this); }, this);
} }

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNet.Server.Kestrel.Infrastructure;
namespace Microsoft.AspNet.Server.Kestrel.Http namespace Microsoft.AspNet.Server.Kestrel.Http
{ {
@ -14,5 +15,22 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{ {
void Write(ArraySegment<byte> buffer, bool immediate = true); void Write(ArraySegment<byte> buffer, bool immediate = true);
Task WriteAsync(ArraySegment<byte> buffer, bool immediate = true, CancellationToken cancellationToken = default(CancellationToken)); Task WriteAsync(ArraySegment<byte> buffer, bool immediate = true, CancellationToken cancellationToken = default(CancellationToken));
/// <summary>
/// Returns an iterator pointing to the tail of the response buffer. Response data can be appended
/// manually or by using <see cref="MemoryPoolIterator2.CopyFrom(ArraySegment{byte})"/>.
/// Be careful to ensure all appended blocks are backed by a <see cref="MemoryPoolSlab2"/>.
/// </summary>
MemoryPoolIterator2 ProducingStart();
/// <summary>
/// Commits the response data appended to the iterator returned from <see cref="ProducingStart"/>.
/// All the data up to <paramref name="end"/> will be included in the response.
/// A write operation isn't guaranteed to be scheduled unless <see cref="Write(ArraySegment{byte}, bool)"/>
/// or <see cref="WriteAsync(ArraySegment{byte}, bool, CancellationToken)"/> is called afterwards.
/// </summary>
/// <param name="end">Points to the end of the committed data.</param>
/// <param name="count">The number of bytes added to the response.</param>
void ProducingComplete(MemoryPoolIterator2 end, int count);
} }
} }

View File

@ -64,7 +64,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{ {
_pinned = _tail; _pinned = _tail;
var data = new ArraySegment<byte>(_pinned.Data.Array, _pinned.End, _pinned.Data.Offset + _pinned.Data.Count - _pinned.End); var data = new ArraySegment<byte>(_pinned.Data.Array, _pinned.End, _pinned.Data.Offset + _pinned.Data.Count - _pinned.End);
var dataPtr = _pinned.Pin(); var dataPtr = _pinned.Pin() + _pinned.End;
return new IncomingBuffer return new IncomingBuffer
{ {
Data = data, Data = data,
@ -77,7 +77,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
return new IncomingBuffer return new IncomingBuffer
{ {
Data = _pinned.Data, Data = _pinned.Data,
DataPtr = _pinned.Pin() DataPtr = _pinned.Pin() + _pinned.End
}; };
} }

View File

@ -23,8 +23,18 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
private readonly long _connectionId; private readonly long _connectionId;
private readonly IKestrelTrace _log; private readonly IKestrelTrace _log;
// This locks all access to _tail, _isProducing and _returnFromOnProducingComplete.
// _head does not require a lock, since it is only used in the ctor and uv thread.
private readonly object _returnLock = new object();
private MemoryPoolBlock2 _head;
private MemoryPoolBlock2 _tail;
private bool _isProducing;
private MemoryPoolBlock2 _returnFromOnProducingComplete;
// This locks access to to all of the below fields // This locks access to to all of the below fields
private readonly object _lockObj = new object(); private readonly object _contextLock = new object();
// The number of write operations that have been scheduled so far // The number of write operations that have been scheduled so far
// but have not completed. // but have not completed.
@ -38,6 +48,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
public SocketOutput( public SocketOutput(
KestrelThread thread, KestrelThread thread,
UvStreamHandle socket, UvStreamHandle socket,
MemoryPool2 memory,
Connection connection, Connection connection,
long connectionId, long connectionId,
IKestrelTrace log) IKestrelTrace log)
@ -48,6 +59,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
_connectionId = connectionId; _connectionId = connectionId;
_log = log; _log = log;
_tasksPending = new Queue<TaskCompletionSource<object>>(); _tasksPending = new Queue<TaskCompletionSource<object>>();
_head = memory.Lease();
_tail = _head;
} }
public Task WriteAsync( public Task WriteAsync(
@ -56,28 +70,20 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
bool socketShutdownSend = false, bool socketShutdownSend = false,
bool socketDisconnect = false) bool socketDisconnect = false)
{ {
//TODO: need buffering that works var tail = ProducingStart();
if (buffer.Array != null) tail = tail.CopyFrom(buffer);
{ // We do our own accounting below
var copy = new byte[buffer.Count]; ProducingComplete(tail, count: 0);
Array.Copy(buffer.Array, buffer.Offset, copy, 0, buffer.Count);
buffer = new ArraySegment<byte>(copy);
_log.ConnectionWrite(_connectionId, buffer.Count);
}
TaskCompletionSource<object> tcs = null; TaskCompletionSource<object> tcs = null;
lock (_lockObj) lock (_contextLock)
{ {
if (_nextWriteContext == null) if (_nextWriteContext == null)
{ {
_nextWriteContext = new WriteContext(this); _nextWriteContext = new WriteContext(this);
} }
if (buffer.Array != null)
{
_nextWriteContext.Buffers.Enqueue(buffer);
}
if (socketShutdownSend) if (socketShutdownSend)
{ {
_nextWriteContext.SocketShutdownSend = true; _nextWriteContext.SocketShutdownSend = true;
@ -138,6 +144,58 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
} }
} }
public MemoryPoolIterator2 ProducingStart()
{
lock (_returnLock)
{
Debug.Assert(!_isProducing);
_isProducing = true;
if (_tail == null)
{
throw new IOException("The socket has been closed.");
}
return new MemoryPoolIterator2(_tail, _tail.End);
}
}
public void ProducingComplete(MemoryPoolIterator2 end, int count)
{
lock (_returnLock)
{
Debug.Assert(_isProducing);
_isProducing = false;
if (_returnFromOnProducingComplete == null)
{
_tail = end.Block;
_tail.End = end.Index;
if (count != 0)
{
lock (_contextLock)
{
_numBytesPreCompleted += count;
}
}
}
else
{
var block = _returnFromOnProducingComplete;
while (block != null)
{
var returnBlock = block;
block = block.Next;
returnBlock.Pool?.Return(returnBlock);
}
_returnFromOnProducingComplete = null;
}
}
}
private void ScheduleWrite() private void ScheduleWrite()
{ {
_thread.Post(_this => _this.WriteAllPending(), this); _thread.Post(_this => _this.WriteAllPending(), this);
@ -148,7 +206,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{ {
WriteContext writingContext; WriteContext writingContext;
lock (_lockObj) lock (_contextLock)
{ {
if (_nextWriteContext != null) if (_nextWriteContext != null)
{ {
@ -168,7 +226,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
} }
catch catch
{ {
lock (_lockObj) lock (_contextLock)
{ {
// Lock instead of using Interlocked.Decrement so _writesSending // Lock instead of using Interlocked.Decrement so _writesSending
// doesn't change in the middle of executing other synchronized code. // doesn't change in the middle of executing other synchronized code.
@ -180,7 +238,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
} }
// This is called on the libuv event loop // This is called on the libuv event loop
private void OnWriteCompleted(Queue<ArraySegment<byte>> writtenBuffers, int status, Exception error) private void OnWriteCompleted(int bytesWritten, int status, Exception error)
{ {
_log.ConnectionWriteCallback(_connectionId, status); _log.ConnectionWriteCallback(_connectionId, status);
@ -192,7 +250,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
_connection.Abort(); _connection.Abort();
} }
lock (_lockObj) lock (_contextLock)
{ {
if (_nextWriteContext != null) if (_nextWriteContext != null)
{ {
@ -203,13 +261,10 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
_writesPending--; _writesPending--;
} }
foreach (var writeBuffer in writtenBuffers) // _numBytesPreCompleted can temporarily go negative in the event there are
{ // completed writes that we haven't triggered callbacks for yet.
// _numBytesPreCompleted can temporarily go negative in the event there are _numBytesPreCompleted -= bytesWritten;
// completed writes that we haven't triggered callbacks for yet.
_numBytesPreCompleted -= writeBuffer.Count;
}
// bytesLeftToBuffer can be greater than _maxBytesPreCompleted // bytesLeftToBuffer can be greater than _maxBytesPreCompleted
// This allows large writes to complete once they've actually finished. // This allows large writes to complete once they've actually finished.
var bytesLeftToBuffer = _maxBytesPreCompleted - _numBytesPreCompleted; var bytesLeftToBuffer = _maxBytesPreCompleted - _numBytesPreCompleted;
@ -225,20 +280,48 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
if (_lastWriteError == null) if (_lastWriteError == null)
{ {
ThreadPool.QueueUserWorkItem( ThreadPool.QueueUserWorkItem(
(o) => ((TaskCompletionSource<object>)o).SetResult(null), (o) => ((TaskCompletionSource<object>)o).SetResult(null),
tcs); tcs);
} }
else else
{ {
// error is closure captured // error is closure captured
ThreadPool.QueueUserWorkItem( ThreadPool.QueueUserWorkItem(
(o) => ((TaskCompletionSource<object>)o).SetException(_lastWriteError), (o) => ((TaskCompletionSource<object>)o).SetException(_lastWriteError),
tcs); tcs);
} }
} }
}
}
// Now that the while loop has completed the following invariants should hold true: // This is called on the libuv event loop
Debug.Assert(_numBytesPreCompleted >= 0); private void ReturnAllBlocks()
{
lock (_returnLock)
{
var block = _head;
while (block != _tail)
{
var returnBlock = block;
block = block.Next;
returnBlock.Unpin();
returnBlock.Pool?.Return(returnBlock);
}
_tail.Unpin();
if (_isProducing)
{
_returnFromOnProducingComplete = _tail;
}
else
{
_tail.Pool?.Return(_tail);
}
_head = null;
_tail = null;
} }
} }
@ -263,9 +346,13 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
private class WriteContext private class WriteContext
{ {
private MemoryPoolIterator2 _lockedStart;
private MemoryPoolIterator2 _lockedEnd;
private int _bufferCount;
private int _byteCount;
public SocketOutput Self; public SocketOutput Self;
public Queue<ArraySegment<byte>> Buffers;
public bool SocketShutdownSend; public bool SocketShutdownSend;
public bool SocketDisconnect; public bool SocketDisconnect;
@ -277,7 +364,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
public WriteContext(SocketOutput self) public WriteContext(SocketOutput self)
{ {
Self = self; Self = self;
Buffers = new Queue<ArraySegment<byte>>();
} }
/// <summary> /// <summary>
@ -285,30 +371,28 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
/// </summary> /// </summary>
public void DoWriteIfNeeded() public void DoWriteIfNeeded()
{ {
if (Buffers.Count == 0 || Self._socket.IsClosed) LockWrite();
if (_byteCount == 0 || Self._socket.IsClosed)
{ {
DoShutdownIfNeeded(); DoShutdownIfNeeded();
return; return;
} }
var buffers = new ArraySegment<byte>[Buffers.Count];
var i = 0;
foreach (var buffer in Buffers)
{
buffers[i++] = buffer;
}
var writeReq = new UvWriteReq(Self._log); var writeReq = new UvWriteReq(Self._log);
writeReq.Init(Self._thread.Loop); writeReq.Init(Self._thread.Loop);
writeReq.Write(Self._socket, new ArraySegment<ArraySegment<byte>>(buffers), (_writeReq, status, error, state) => writeReq.Write(Self._socket, _lockedStart, _lockedEnd, _bufferCount, (_writeReq, status, error, state) =>
{ {
_writeReq.Dispose(); _writeReq.Dispose();
var _this = (WriteContext)state; var _this = (WriteContext)state;
_this.ReturnFullyWrittenBlocks();
_this.WriteStatus = status; _this.WriteStatus = status;
_this.WriteError = error; _this.WriteError = error;
_this.DoShutdownIfNeeded(); _this.DoShutdownIfNeeded();
}, this); }, this);
Self._head = _lockedEnd.Block;
Self._head.Start = _lockedEnd.Index;
} }
/// <summary> /// <summary>
@ -348,13 +432,62 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
} }
Self._socket.Dispose(); Self._socket.Dispose();
Self.ReturnAllBlocks();
Self._log.ConnectionStop(Self._connectionId); Self._log.ConnectionStop(Self._connectionId);
Complete(); Complete();
} }
public void Complete() public void Complete()
{ {
Self.OnWriteCompleted(Buffers, WriteStatus, WriteError); Self.OnWriteCompleted(_byteCount, WriteStatus, WriteError);
}
private void ReturnFullyWrittenBlocks()
{
var block = _lockedStart.Block;
while (block != _lockedEnd.Block)
{
var returnBlock = block;
block = block.Next;
returnBlock.Unpin();
returnBlock.Pool?.Return(returnBlock);
}
}
private void LockWrite()
{
var head = Self._head;
var tail = Self._tail;
if (head == null || tail == null)
{
// ReturnAllBlocks has already bee called. Nothing to do here.
// Write will no-op since _byteCount will remain 0.
return;
}
_lockedStart = new MemoryPoolIterator2(head, head.Start);
_lockedEnd = new MemoryPoolIterator2(tail, tail.End);
if (_lockedStart.Block == _lockedEnd.Block)
{
_byteCount = _lockedEnd.Index - _lockedStart.Index;
_bufferCount = 1;
return;
}
_byteCount = _lockedStart.Block.Data.Offset + _lockedStart.Block.Data.Count - _lockedStart.Index;
_bufferCount = 1;
for (var block = _lockedStart.Block.Next; block != _lockedEnd.Block; block = block.Next)
{
_byteCount += block.Data.Count;
_bufferCount++;
}
_byteCount += _lockedEnd.Index - _lockedEnd.Block.Data.Offset;
_bufferCount++;
} }
} }
} }

View File

@ -59,7 +59,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
/// <param name="minimumSize">The block returned must be at least this size. It may be larger than this minimum size, and if so, /// <param name="minimumSize">The block returned must be at least this size. It may be larger than this minimum size, and if so,
/// the caller may write to the block's entire size rather than being limited to the minumumSize requested.</param> /// the caller may write to the block's entire size rather than being limited to the minumumSize requested.</param>
/// <returns>The block that is reserved for the called. It must be passed to Return when it is no longer being used.</returns> /// <returns>The block that is reserved for the called. It must be passed to Return when it is no longer being used.</returns>
public MemoryPoolBlock2 Lease(int minimumSize) public MemoryPoolBlock2 Lease(int minimumSize = _blockLength)
{ {
if (minimumSize > _blockLength) if (minimumSize > _blockLength)
{ {

View File

@ -98,24 +98,27 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
} }
/// <summary> /// <summary>
/// Called to ensure that a block is pinned, and return the pointer to native memory just after /// Called to ensure that a block is pinned, and return the pointer to the native address
/// the range of "active" bytes. This is where arriving data is read into. /// of the first byte of this block's Data memory. Arriving data is read into Pin() + End.
/// Outgoing data is read from Pin() + Start.
/// </summary> /// </summary>
/// <returns></returns> /// <returns></returns>
public IntPtr Pin() public IntPtr Pin()
{ {
Debug.Assert(!_pinHandle.IsAllocated);
if (_dataArrayPtr != IntPtr.Zero) if (_dataArrayPtr != IntPtr.Zero)
{ {
// this is a slab managed block - use the native address of the slab which is always locked // this is a slab managed block - use the native address of the slab which is always locked
return _dataArrayPtr + End; return _dataArrayPtr;
}
else if (_pinHandle.IsAllocated)
{
return _pinHandle.AddrOfPinnedObject();
} }
else else
{ {
// this is one-time-use memory - lock the managed memory until Unpin is called // this is one-time-use memory - lock the managed memory until Unpin is called
_pinHandle = GCHandle.Alloc(Data.Array, GCHandleType.Pinned); _pinHandle = GCHandle.Alloc(Data.Array, GCHandleType.Pinned);
return _pinHandle.AddrOfPinnedObject() + End; return _pinHandle.AddrOfPinnedObject();
} }
} }

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
@ -530,5 +531,46 @@ namespace Microsoft.AspNet.Server.Kestrel.Infrastructure
} }
} }
} }
public MemoryPoolIterator2 CopyFrom(ArraySegment<byte> buffer)
{
Debug.Assert(_block != null);
Debug.Assert(_block.Pool != null);
Debug.Assert(_block.Next == null);
Debug.Assert(_block.End == _index);
var pool = _block.Pool;
var block = _block;
var blockIndex = _index;
var bufferIndex = buffer.Offset;
var remaining = buffer.Count;
while (remaining > 0)
{
var bytesLeftInBlock = block.Data.Offset + block.Data.Count - blockIndex;
if (bytesLeftInBlock == 0)
{
var nextBlock = pool.Lease();
block.Next = nextBlock;
block = nextBlock;
blockIndex = block.Data.Offset;
bytesLeftInBlock = block.Data.Count;
}
var bytesToCopy = Math.Min(remaining, bytesLeftInBlock);
Buffer.BlockCopy(buffer.Array, bufferIndex, block.Array, blockIndex, bytesToCopy);
blockIndex += bytesToCopy;
bufferIndex += bytesToCopy;
remaining -= bytesToCopy;
block.End = blockIndex;
}
return new MemoryPoolIterator2(block, blockIndex);
}
} }
} }

View File

@ -41,7 +41,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
public unsafe void Write( public unsafe void Write(
UvStreamHandle handle, UvStreamHandle handle,
ArraySegment<ArraySegment<byte>> bufs, MemoryPoolIterator2 start,
MemoryPoolIterator2 end,
int nBuffers,
Action<UvWriteReq, int, Exception, object> callback, Action<UvWriteReq, int, Exception, object> callback,
object state) object state)
{ {
@ -51,7 +53,6 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
_pins.Add(GCHandle.Alloc(this, GCHandleType.Normal)); _pins.Add(GCHandle.Alloc(this, GCHandleType.Normal));
var pBuffers = (Libuv.uv_buf_t*)_bufs; var pBuffers = (Libuv.uv_buf_t*)_bufs;
var nBuffers = bufs.Count;
if (nBuffers > BUFFER_COUNT) if (nBuffers > BUFFER_COUNT)
{ {
// create and pin buffer array when it's larger than the pre-allocated one // create and pin buffer array when it's larger than the pre-allocated one
@ -61,16 +62,18 @@ namespace Microsoft.AspNet.Server.Kestrel.Networking
pBuffers = (Libuv.uv_buf_t*)gcHandle.AddrOfPinnedObject(); pBuffers = (Libuv.uv_buf_t*)gcHandle.AddrOfPinnedObject();
} }
var block = start.Block;
for (var index = 0; index < nBuffers; index++) for (var index = 0; index < nBuffers; index++)
{ {
// create and pin each segment being written var blockStart = block == start.Block ? start.Index : block.Data.Offset;
var buf = bufs.Array[bufs.Offset + index]; var blockEnd = block == end.Block ? end.Index : block.Data.Offset + block.Data.Count;
var gcHandle = GCHandle.Alloc(buf.Array, GCHandleType.Pinned); // create and pin each segment being written
_pins.Add(gcHandle);
pBuffers[index] = Libuv.buf_init( pBuffers[index] = Libuv.buf_init(
gcHandle.AddrOfPinnedObject() + buf.Offset, block.Pin() + blockStart,
buf.Count); blockEnd - blockStart);
block = block.Next;
} }
_callback = callback; _callback = callback;

View File

@ -1,4 +1,5 @@
using System.Linq; using System;
using System.Linq;
using Microsoft.AspNet.Server.Kestrel.Infrastructure; using Microsoft.AspNet.Server.Kestrel.Infrastructure;
using Xunit; using Xunit;
@ -152,6 +153,38 @@ namespace Microsoft.AspNet.Server.KestrelTests
} }
} }
[Fact]
public void CopyFromCorrectlyTraversesBlocks()
{
using (var pool = new MemoryPool2())
{
var block1 = pool.Lease(128);
var iterator = block1.GetIterator();
var bufferSize = block1.Data.Count * 3;
var buffer = new byte[bufferSize];
for (int i = 0; i < bufferSize; i++)
{
buffer[i] = (byte)(i % 73);
}
Assert.Null(block1.Next);
var end = iterator.CopyFrom(new ArraySegment<byte>(buffer));
Assert.NotNull(block1.Next);
for (int i = 0; i < bufferSize; i++)
{
Assert.Equal(i % 73, iterator.Take());
}
Assert.Equal(-1, iterator.Take());
Assert.Equal(iterator.Block, end.Block);
Assert.Equal(iterator.Index, end.Index);
}
}
[Fact] [Fact]
public void IsEndCorrectlyTraversesBlocks() public void IsEndCorrectlyTraversesBlocks()
{ {

View File

@ -62,14 +62,24 @@ namespace Microsoft.AspNet.Server.KestrelTests
var writeRequest = new UvWriteReq(new KestrelTrace(new TestKestrelTrace())); var writeRequest = new UvWriteReq(new KestrelTrace(new TestKestrelTrace()));
writeRequest.Init(loop); writeRequest.Init(loop);
var block = MemoryPoolBlock2.Create(
new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 }),
dataPtr: IntPtr.Zero,
pool: null,
slab: null);
var start = new MemoryPoolIterator2(block, 0);
var end = new MemoryPoolIterator2(block, block.Data.Count);
writeRequest.Write( writeRequest.Write(
serverConnectionPipe, serverConnectionPipe,
new ArraySegment<ArraySegment<byte>>(new ArraySegment<byte>[] { new ArraySegment<byte>(new byte[] { 1, 2, 3, 4 }) }), start,
end,
1,
(_3, status2, error2, _4) => (_3, status2, error2, _4) =>
{ {
writeRequest.Dispose(); writeRequest.Dispose();
serverConnectionPipe.Dispose(); serverConnectionPipe.Dispose();
serverListenPipe.Dispose(); serverListenPipe.Dispose();
block.Unpin();
}, },
null); null);

View File

@ -201,12 +201,22 @@ namespace Microsoft.AspNet.Server.KestrelTests
{ {
var req = new UvWriteReq(new KestrelTrace(new TestKestrelTrace())); var req = new UvWriteReq(new KestrelTrace(new TestKestrelTrace()));
req.Init(loop); req.Init(loop);
var block = MemoryPoolBlock2.Create(
new ArraySegment<byte>(new byte[] { 65, 66, 67, 68, 69 }),
dataPtr: IntPtr.Zero,
pool: null,
slab: null);
var start = new MemoryPoolIterator2(block, 0);
var end = new MemoryPoolIterator2(block, block.Data.Count);
req.Write( req.Write(
tcp2, tcp2,
new ArraySegment<ArraySegment<byte>>( start,
new[] { new ArraySegment<byte>(new byte[] { 65, 66, 67, 68, 69 }) } end,
), 1,
(_1, _2, _3, _4) => { }, (_1, _2, _3, _4) =>
{
block.Unpin();
},
null); null);
} }
} }

View File

@ -34,13 +34,14 @@ namespace Microsoft.AspNet.Server.KestrelTests
}; };
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext())) using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
using (var memory = new MemoryPool2())
{ {
kestrelEngine.Start(count: 1); kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0]; var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace());
var socketOutput = new SocketOutput(kestrelThread, socket, null, 0, trace); var socketOutput = new SocketOutput(kestrelThread, socket, memory, null, 0, trace);
// I doubt _maxBytesPreCompleted will ever be over a MB. If it is, we should change this test. // I doubt _maxBytesPreCompleted will ever be over a MB. If it is, we should change this test.
var bufferSize = 1048576; var bufferSize = 1048576;
@ -79,13 +80,14 @@ namespace Microsoft.AspNet.Server.KestrelTests
}; };
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext())) using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
using (var memory = new MemoryPool2())
{ {
kestrelEngine.Start(count: 1); kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0]; var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace());
var socketOutput = new SocketOutput(kestrelThread, socket, null, 0, trace); var socketOutput = new SocketOutput(kestrelThread, socket, memory, null, 0, trace);
var bufferSize = maxBytesPreCompleted; var bufferSize = maxBytesPreCompleted;
var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize); var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
@ -134,13 +136,14 @@ namespace Microsoft.AspNet.Server.KestrelTests
}; };
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext())) using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
using (var memory = new MemoryPool2())
{ {
kestrelEngine.Start(count: 1); kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0]; var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace());
var socketOutput = new SocketOutput(kestrelThread, socket, null, 0, trace); var socketOutput = new SocketOutput(kestrelThread, socket, memory, null, 0, trace);
var bufferSize = maxBytesPreCompleted; var bufferSize = maxBytesPreCompleted;
@ -213,13 +216,14 @@ namespace Microsoft.AspNet.Server.KestrelTests
}; };
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext())) using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
using (var memory = new MemoryPool2())
{ {
kestrelEngine.Start(count: 1); kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0]; var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace()); var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace()); var trace = new KestrelTrace(new TestKestrelTrace());
var socketOutput = new SocketOutput(kestrelThread, socket, null, 0, trace); var socketOutput = new SocketOutput(kestrelThread, socket, memory, null, 0, trace);
var bufferSize = maxBytesPreCompleted; var bufferSize = maxBytesPreCompleted;
var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize); var buffer = new ArraySegment<byte>(new byte[bufferSize], 0, bufferSize);
@ -270,6 +274,57 @@ namespace Microsoft.AspNet.Server.KestrelTests
} }
} }
[Fact]
public void ProducingStartAndProducingCompleteCanBeUsedDirectly()
{
int nBuffers = 0;
var nBufferWh = new ManualResetEventSlim();
var mockLibuv = new MockLibuv
{
OnWrite = (socket, buffers, triggerCompleted) =>
{
nBuffers = buffers;
nBufferWh.Set();
triggerCompleted(0);
return 0;
}
};
using (var kestrelEngine = new KestrelEngine(mockLibuv, new TestServiceContext()))
using (var memory = new MemoryPool2())
{
kestrelEngine.Start(count: 1);
var kestrelThread = kestrelEngine.Threads[0];
var socket = new MockSocket(kestrelThread.Loop.ThreadId, new TestKestrelTrace());
var trace = new KestrelTrace(new TestKestrelTrace());
var socketOutput = new SocketOutput(kestrelThread, socket, memory, null, 0, trace);
// block 1
var start = socketOutput.ProducingStart();
start.Block.End = start.Block.Data.Offset + start.Block.Data.Count;
var totalBytes = start.Block.Data.Count;
// block 2
var block2 = memory.Lease();
block2.End = block2.Data.Offset + block2.Data.Count;
start.Block.Next = block2;
totalBytes += block2.Data.Count;
var end = new MemoryPoolIterator2(block2, block2.End);
socketOutput.ProducingComplete(end, totalBytes);
// A call to Write is required to ensure a write is scheduled
socketOutput.WriteAsync(default(ArraySegment<byte>));
Assert.True(nBufferWh.Wait(1000));
Assert.Equal(2, nBuffers);
}
}
private class MockSocket : UvStreamHandle private class MockSocket : UvStreamHandle
{ {
public MockSocket(int threadId, IKestrelTrace logger) : base(logger) public MockSocket(int threadId, IKestrelTrace logger) : base(logger)

View File

@ -12,7 +12,7 @@ namespace Microsoft.AspNet.Server.KestrelTests.TestHelpers
private bool _stopLoop; private bool _stopLoop;
private readonly ManualResetEventSlim _loopWh = new ManualResetEventSlim(); private readonly ManualResetEventSlim _loopWh = new ManualResetEventSlim();
private Func<UvStreamHandle, ArraySegment<ArraySegment<byte>>, Action<int>, int> _onWrite; private Func<UvStreamHandle, int, Action<int>, int> _onWrite;
unsafe public MockLibuv() unsafe public MockLibuv()
{ {
@ -68,7 +68,7 @@ namespace Microsoft.AspNet.Server.KestrelTests.TestHelpers
_uv_walk = (loop, callback, ignore) => 0; _uv_walk = (loop, callback, ignore) => 0;
} }
public Func<UvStreamHandle, ArraySegment<ArraySegment<byte>>, Action<int>, int> OnWrite public Func<UvStreamHandle, int, Action<int>, int> OnWrite
{ {
get get
{ {
@ -82,7 +82,7 @@ namespace Microsoft.AspNet.Server.KestrelTests.TestHelpers
unsafe private int UvWrite(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, uv_write_cb cb) unsafe private int UvWrite(UvRequest req, UvStreamHandle handle, uv_buf_t* bufs, int nbufs, uv_write_cb cb)
{ {
return _onWrite(handle, new ArraySegment<ArraySegment<byte>>(), status => cb(req.InternalGetHandle(), status)); return _onWrite(handle, nbufs, status => cb(req.InternalGetHandle(), status));
} }
} }
} }