diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPool2.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPool2.cs
index 9256bdb314..941fb9b796 100644
--- a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPool2.cs
+++ b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPool2.cs
@@ -3,22 +3,70 @@ using System.Collections.Concurrent;
namespace Microsoft.AspNet.Server.Kestrel.Http
{
+ ///
+ /// Used to allocate and distribute re-usable blocks of memory.
+ ///
public class MemoryPool2 : IDisposable
{
+ ///
+ /// The gap between blocks' starting address. 4096 is chosen because most operating systems are 4k pages in size and alignment.
+ ///
private const int blockStride = 4096;
+
+ ///
+ /// The last 64 bytes of a block are unused to prevent CPU from pre-fetching the next 64 byte into it's memory cache.
+ /// See https://github.com/aspnet/KestrelHttpServer/issues/117 and https://www.youtube.com/watch?v=L7zSU9HI-6I
+ ///
private const int blockUnused = 64;
+
+ ///
+ /// Allocating 32 contiguous blocks per slab makes the slab size 128k. This is larger than the 85k size which will place the memory
+ /// in the large object heap. This means the GC will not try to relocate this array, so the fact it remains pinned does not negatively
+ /// affect memory management's compactification.
+ ///
private const int blockCount = 32;
+
+ ///
+ /// 4096 - 64 gives you a blockLength of 4032 usable bytes per block.
+ ///
private const int blockLength = blockStride - blockUnused;
+
+ ///
+ /// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab
+ ///
private const int slabLength = blockStride * blockCount;
- private ConcurrentStack _blocks = new ConcurrentStack();
- private ConcurrentStack _slabs = new ConcurrentStack();
+ ///
+ /// Thread-safe collection of blocks which are currently in the pool. A slab will pre-allocate all of the block tracking objects
+ /// and add them to this collection. When memory is requested it is taken from here first, and when it is returned it is re-added.
+ ///
+ private readonly ConcurrentStack _blocks = new ConcurrentStack();
+
+ ///
+ /// Thread-safe collection of slabs which have been allocated by this pool. As long as a slab is in this collection and slab.IsActive,
+ /// the blocks will be added to _blocks when returned.
+ ///
+ private readonly ConcurrentStack _slabs = new ConcurrentStack();
+
+ ///
+ /// This is part of implementing the IDisposable pattern.
+ ///
private bool disposedValue = false; // To detect redundant calls
+ ///
+ /// Called to take a block from the pool.
+ ///
+ /// 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.
+ /// The block that is reserved for the called. It must be passed to Return when it is no longer being used.
public MemoryPoolBlock2 Lease(int minimumSize)
{
if (minimumSize > blockLength)
{
+ // The requested minimumSize is actually larger then the usable memory of a single block.
+ // Because this is the degenerate case, a one-time-use byte[] array and tracking object are allocated.
+ // When this block tracking object is returned it is not added to the pool - instead it will be
+ // allowed to be garbace collected normally.
return MemoryPoolBlock2.Create(
new ArraySegment(new byte[minimumSize]),
dataPtr: IntPtr.Zero,
@@ -31,12 +79,18 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
MemoryPoolBlock2 block;
if (_blocks.TryPop(out block))
{
+ // block successfully taken from the stack - return it
return block;
}
+ // no blocks available - grow the pool and try again
AllocateSlab();
}
}
+ ///
+ /// Internal method called when a block is requested and the pool is empty. It allocates one additional slab, creates all of the
+ /// block tracking objects, and adds them all to the pool.
+ ///
private void AllocateSlab()
{
var slab = MemoryPoolSlab2.Create(slabLength);
@@ -58,6 +112,14 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
}
}
+ ///
+ /// Called to return a block to the pool. Once Return has been called the memory no longer belongs to the caller, and
+ /// Very Bad Things will happen if the memory is read of modified subsequently. If a caller fails to call Return and the
+ /// block tracking object is garbage collected, the block tracking object's finalizer will automatically re-create and return
+ /// a new tracking object into the pool. This will only happen if there is a bug in the server, however it is necessary to avoid
+ /// leaving "dead zones" in the slab due to lost block tracking objects.
+ ///
+ /// The block to return. It must have been acquired by calling Lease on the same memory pool instance.
public void Return(MemoryPoolBlock2 block)
{
block.Reset();
diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolBlock2.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolBlock2.cs
index 7d2f8d27d1..f2a47c35d9 100644
--- a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolBlock2.cs
+++ b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolBlock2.cs
@@ -8,33 +8,93 @@ using System.Text;
namespace Microsoft.AspNet.Server.Kestrel.Http
{
+ ///
+ /// Block tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
+ /// individual blocks are then treated as independant array segments.
+ ///
public class MemoryPoolBlock2
{
- private static Vector _dotIndex = new Vector(Enumerable.Range(0, Vector.Count).Select(x => (byte)-x).ToArray());
+ ///
+ /// Array of "minus one" bytes of the length of SIMD operations on the current hardware. Used as an argument in the
+ /// vector dot product that counts matching character occurence.
+ ///
private static Vector _dotCount = new Vector(Byte.MaxValue);
+ ///
+ /// Array of negative numbers starting at 0 and continuing for the length of SIMD operations on the current hardware.
+ /// Used as an argument in the vector dot product that determines matching character index.
+ ///
+ private static Vector _dotIndex = new Vector(Enumerable.Range(0, Vector.Count).Select(x => (byte)-x).ToArray());
+
+ ///
+ /// If this block represents a one-time-use memory object, this GCHandle will hold that memory object at a fixed address
+ /// so it can be used in native operations.
+ ///
private GCHandle _pinHandle;
+
+ ///
+ /// Native address of the first byte of this block's Data memory. It is null for one-time-use memory, or copied from
+ /// the Slab's ArrayPtr for a slab-block segment. The byte it points to corresponds to Data.Array[0], and in practice you will always
+ /// use the _dataArrayPtr + Start or _dataArrayPtr + End, which point to the start of "active" bytes, or point to just after the "active" bytes.
+ ///
private IntPtr _dataArrayPtr;
+ ///
+ /// The array segment describing the range of memory this block is tracking. The caller which has leased this block may only read and
+ /// modify the memory in this range.
+ ///
public ArraySegment Data;
+ ///
+ /// This object cannot be instantiated outside of the static Create method
+ ///
protected MemoryPoolBlock2()
{
}
+ ///
+ /// Back-reference to the memory pool which this block was allocated from. It may only be returned to this pool.
+ ///
public MemoryPool2 Pool { get; private set; }
+ ///
+ /// Back-reference to the slab from which this block was taken, or null if it is one-time-use memory.
+ ///
public MemoryPoolSlab2 Slab { get; private set; }
+ ///
+ /// Convenience accessor
+ ///
public byte[] Array => Data.Array;
+
+ ///
+ /// The Start represents the offset into Array where the range of "active" bytes begins. At the point when the block is leased
+ /// the Start is guaranteed to be equal to Array.Offset. The value of Start may be assigned anywhere between Data.Offset and
+ /// Data.Offset + Data.Count, and must be equal to or less than End.
+ ///
public int Start { get; set; }
+
+ ///
+ /// The End represents the offset into Array where the range of "active" bytes ends. At the point when the block is leased
+ /// the End is guaranteed to be equal to Array.Offset. The value of Start may be assigned anywhere between Data.Offset and
+ /// Data.Offset + Data.Count, and must be equal to or less than End.
+ ///
public int End { get; set; }
+
+
+ ///
+ /// Reference to the next block of data when the overall "active" bytes spans multiple blocks. At the point when the block is
+ /// leased Next is guaranteed to be null. Start, End, and Next are used together in order to create a linked-list of discontiguous
+ /// working memory. The "active" memory is grown when bytes are copied in, End is increased, and Next is assigned. The "active"
+ /// memory is shrunk when bytes are consumed, Start is increased, and blocks are returned to the pool.
+ ///
public MemoryPoolBlock2 Next { get; set; }
~MemoryPoolBlock2()
{
if (_pinHandle.IsAllocated)
{
+ // if this is a one-time-use block, ensure that the GCHandle does not leak
_pinHandle.Free();
}
@@ -50,16 +110,23 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
}
}
+ ///
+ /// Called to ensure that a block is pinned, and return the pointer to native memory just after
+ /// the range of "active" bytes. This is where arriving data is read into.
+ ///
+ ///
public IntPtr Pin()
{
Debug.Assert(!_pinHandle.IsAllocated);
if (_dataArrayPtr != IntPtr.Zero)
{
+ // this is a slab managed block - use the native address of the slab which is always locked
return _dataArrayPtr + End;
}
else
{
+ // this is one-time-use memory - lock the managed memory until Unpin is called
_pinHandle = GCHandle.Alloc(Data.Array, GCHandleType.Pinned);
return _pinHandle.AddrOfPinnedObject() + End;
}
@@ -69,6 +136,7 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
{
if (_dataArrayPtr == IntPtr.Zero)
{
+ // this is one-time-use memory - unlock the managed memory
Debug.Assert(_pinHandle.IsAllocated);
_pinHandle.Free();
}
@@ -100,6 +168,9 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
};
}
+ ///
+ /// called when the block is returned to the pool. mutable values are re-assigned to their guaranteed initialized state.
+ ///
public void Reset()
{
Next = null;
@@ -107,11 +178,19 @@ namespace Microsoft.AspNet.Server.Kestrel.Http
End = Data.Offset;
}
+ ///
+ /// ToString overridden for debugger convenience. This displays the "active" byte information in this block as ASCII characters.
+ ///
+ ///
public override string ToString()
{
return Encoding.ASCII.GetString(Array, Start, End - Start);
}
+ ///
+ /// acquires a cursor pointing into this block at the Start of "active" byte information
+ ///
+ ///
public Iterator GetIterator()
{
return new Iterator(this);
diff --git a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolSlab2.cs b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolSlab2.cs
index 88b20c78b4..bf0221dda7 100644
--- a/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolSlab2.cs
+++ b/src/Microsoft.AspNet.Server.Kestrel/Http/MemoryPoolSlab2.cs
@@ -5,18 +5,51 @@ using System.Runtime.InteropServices;
namespace Microsoft.AspNet.Server.Kestrel.Http
{
+ ///
+ /// Slab tracking object used by the byte buffer memory pool. A slab is a large allocation which is divided into smaller blocks. The
+ /// individual blocks are then treated as independant array segments.
+ ///
public class MemoryPoolSlab2 : IDisposable
{
+ ///
+ /// This handle pins the managed array in memory until the slab is disposed. This prevents it from being
+ /// relocated and enables any subsections of the array to be used as native memory pointers to P/Invoked API calls.
+ ///
private GCHandle _gcHandle;
+
+ ///
+ /// The managed memory allocated in the large object heap.
+ ///
public byte[] Array;
+
+ ///
+ /// The native memory pointer of the pinned Array. All block native addresses are pointers into the memory
+ /// ranging from ArrayPtr to ArrayPtr + Array.Length
+ ///
public IntPtr ArrayPtr;
+
+ ///
+ /// True as long as the blocks from this slab are to be considered returnable to the pool. In order to shrink the
+ /// memory pool size an entire slab must be removed. That is done by (1) setting IsActive to false and removing the
+ /// slab from the pool's _slabs collection, (2) as each block currently in use is Return()ed to the pool it will
+ /// be allowed to be garbage collected rather than re-pooled, and (3) when all block tracking objects are garbage
+ /// collected and the slab is no longer references the slab will be garbage collected and the memory unpinned will
+ /// be unpinned by the slab's Dispose.
+ ///
public bool IsActive;
+
+ ///
+ /// Part of the IDisposable implementation
+ ///
private bool disposedValue = false; // To detect redundant calls
public static MemoryPoolSlab2 Create(int length)
{
+ // allocate and pin requested memory length
var array = new byte[length];
var gcHandle = GCHandle.Alloc(array, GCHandleType.Pinned);
+
+ // allocate and return slab tracking object
return new MemoryPoolSlab2
{
Array = array,