using System; using System.Collections.Concurrent; using System.Diagnostics; namespace Microsoft.AspNetCore.Server.Kestrel.Infrastructure { /// /// Used to allocate and distribute re-usable blocks of memory. /// public class MemoryPool : 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; /// /// Max allocation block size for pooled blocks, /// larger values can be leased but they will be disposed after use rather than returned to the pool. /// public const int MaxPooledBlockLength = _blockLength; /// /// 4096 * 32 gives you a slabLength of 128k contiguous bytes allocated per slab /// private const int _slabLength = _blockStride * _blockCount; /// /// 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 ConcurrentQueue _blocks = new ConcurrentQueue(); /// /// 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 that is reserved for the called. It must be passed to Return when it is no longer being used. public MemoryPoolBlock Lease() { MemoryPoolBlock block; if (_blocks.TryDequeue(out block)) { // block successfully taken from the stack - return it return block; } // no blocks available - grow the pool return 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 MemoryPoolBlock AllocateSlab() { var slab = MemoryPoolSlab.Create(_slabLength); _slabs.Push(slab); var basePtr = slab.ArrayPtr; var firstOffset = (int)((_blockStride - 1) - ((ulong)(basePtr + _blockStride - 1) % _blockStride)); var poolAllocationLength = _slabLength - _blockStride; var offset = firstOffset; for (; offset + _blockLength < poolAllocationLength; offset += _blockStride) { var block = MemoryPoolBlock.Create( new ArraySegment(slab.Array, offset, _blockLength), basePtr, this, slab); Return(block); } // return last block rather than adding to pool var newBlock = MemoryPoolBlock.Create( new ArraySegment(slab.Array, offset, _blockLength), basePtr, this, slab); return newBlock; } /// /// 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(MemoryPoolBlock block) { Debug.Assert(block.Pool == this, "Returned block was not leased from this pool"); if (block.Slab != null && block.Slab.IsActive) { block.Reset(); _blocks.Enqueue(block); } } protected virtual void Dispose(bool disposing) { if (!_disposedValue) { if (disposing) { MemoryPoolSlab slab; while (_slabs.TryPop(out slab)) { // dispose managed state (managed objects). slab.Dispose(); } } foreach (var block in _blocks) { GC.SuppressFinalize(block); } // N/A: free unmanaged resources (unmanaged objects) and override a finalizer below. // N/A: set large fields to null. _disposedValue = true; } } // N/A: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. // ~MemoryPool2() { // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. // Dispose(false); // } // This code added to correctly implement the disposable pattern. public void Dispose() { // Do not change this code. Put cleanup code in Dispose(bool disposing) above. Dispose(true); // N/A: uncomment the following line if the finalizer is overridden above. // GC.SuppressFinalize(this); } } }