public void TryAllocateTest() { IMemorySlab target = CreateIMemorySlab(); //allocate 1 byte long smallLength = 1; IMemoryBlock allocatedBlock = null; bool result1 = target.TryAllocate(smallLength, out allocatedBlock); Assert.AreEqual <long>(smallLength, allocatedBlock.Length); Assert.AreEqual <long>(0, allocatedBlock.StartLocation); Assert.AreEqual <bool>(true, result1); //allocate rest of slab long restLength = totalSize - 1; IMemoryBlock allocatedBlock2 = null; bool result2 = target.TryAllocate(restLength, out allocatedBlock2); Assert.AreEqual <long>(restLength, allocatedBlock2.Length); Assert.AreEqual <long>(1, allocatedBlock2.StartLocation); Assert.AreEqual <bool>(true, result2); //Now try to allocate another byte and expect it to fail IMemoryBlock allocatedBlock3 = null; bool result3 = target.TryAllocate(smallLength, out allocatedBlock3); Assert.AreEqual <bool>(false, result3); //Clean up target.Free(allocatedBlock); target.Free(allocatedBlock2); }
private object sync_slabList = new object(); //synchronizes access to the array of slabs #endregion Fields #region Constructors /// <summary> /// Initializes a new instance of the BufferPool class /// </summary> /// <param name="slabSize">Length, in bytes, of a slab in the BufferPool</param> /// <param name="initialSlabs">Number of slabs to create initially</param> /// <param name="subsequentSlabs">Number of additional slabs to create at a time</param> public BufferPool(long slabSize, int initialSlabs, int subsequentSlabs) { if (slabSize < 1) throw new ArgumentException("SlabSize must be equal to or greater than 1"); if (initialSlabs < 1) throw new ArgumentException("InitialSlabs must be equal to or greater than 1"); if (subsequentSlabs < 1) throw new ArgumentException("SubsequentSlabs must be equal to or greater than 1"); this.slabSize = slabSize > MinimumSlabSize ? slabSize : MinimumSlabSize; this.initialSlabs = initialSlabs; this.subsequentSlabs = subsequentSlabs; lock (sync_slabList) { if (slabs.Count == 0) { if (initialSlabs > 1) { Interlocked.Exchange(ref singleSlabPool, 0); //false } else { Interlocked.Exchange(ref singleSlabPool, -1); //true } for (int i = 0; i < initialSlabs; i++) { slabs.Add(new MemorySlab(slabSize, this)); } firstSlab = slabs[0]; } } }
public void LargestFreeBlockSizeTest() { IMemorySlab target = CreateIMemorySlab(); long actual; //LargestFreeBlockSize should be initially the total size of the slab actual = target.LargestFreeBlockSize; Assert.AreEqual <long>(totalSize, actual); //Allocate a small block, LargestFreeBlockSize should be the difference from the total slab size IMemoryBlock allocatedBlock; target.TryAllocate(4321, out allocatedBlock); actual = target.LargestFreeBlockSize; Assert.AreEqual <long>(totalSize - 4321, actual); //Allocate a bigger block, LargestFreeBlockSize should decrease by that block size IMemoryBlock allocatedBlock2; target.TryAllocate(19500, out allocatedBlock2); actual = target.LargestFreeBlockSize; Assert.AreEqual <long>(totalSize - 4321 - 19500, actual); //Free the original small allocation target.Free(allocatedBlock); //LargestFreeBlockSize should be the greater of it's previous and the freed block Assert.AreEqual <long>(Math.Max(4321, (totalSize - 4321 - 19500)), actual); target.Free(allocatedBlock2); }
/// <summary> /// Initializes a new instance of the ManagedBuffer class, specifying the slab to be associated with the ManagedBuffer. /// This constructor creates an empty (zero-length) buffer. /// </summary> /// <param name="slab">The Memory Slab to be associated with the ManagedBuffer</param> internal ManagedBuffer(IMemorySlab slab) { if (slab == null) throw new ArgumentNullException("slab"); memoryBlocks = null; this.slabArray = slab.Array; size = 0; }
public void ArrayTest() { IMemorySlab target = CreateIMemorySlab(); byte[] actual; actual = target.Array; Assert.AreEqual <long>(totalSize, actual.LongLength); }
public void SizeTest() { IMemorySlab target = CreateIMemorySlab(); long actual; actual = target.Size; Assert.AreEqual <long>(totalSize, actual); }
/// <summary> /// Initializes a new instance of the ManagedBuffer class, specifying the slab to be associated with the ManagedBuffer. /// This constructor creates an empty (zero-length) buffer. /// </summary> /// <param name="slab">The Memory Slab to be associated with the ManagedBuffer</param> internal ManagedBuffer(IMemorySlab slab) { if (slab == null) { throw new ArgumentNullException("SlabArray"); } memoryBlock = null; this.slabArray = slab.Array; }
public void TryAllocateTest3() { IMemorySlab target = CreateIMemorySlab(); //allocate invalid length long badLength = -1; IMemoryBlock allocatedBlock = null; bool result1 = target.TryAllocate(badLength, out allocatedBlock); }
//Gets a single slab buffer private static ManagedBuffer GetNewBuffer(IMemorySlab Slab) { IMemoryBlock allocatedMemoryBlock; Slab.TryAllocate(blockSize, out allocatedMemoryBlock); ManagedBuffer target = new ManagedBuffer(new IMemoryBlock[] { allocatedMemoryBlock }); return(target); }
/// <summary> /// Initializes a new instance of the MemoryBlock class /// </summary> /// <param name="startLocation">Offset where memory block starts in slab</param> /// <param name="length">Length of memory block</param> /// <param name="slab">Slab to be associated with the memory block</param> internal MemoryBlock(long startLocation, long length, IMemorySlab slab) { if (startLocation < 0) { throw new ArgumentOutOfRangeException("startLocation", "StartLocation must be greater than 0"); } startLoc = startLocation; if (length <= 0) { throw new ArgumentOutOfRangeException("length", "Length must be greater than 0"); } endLoc = startLocation + length - 1; this.length = length; if (slab == null) throw new ArgumentNullException("slab"); this.owner = slab; }
public void FreeTest() { IMemorySlab target = CreateIMemorySlab(); IMemoryBlock block1, block2, block3, block4, block5, block6; target.TryAllocate(10, out block1); target.TryAllocate(10, out block2); target.TryAllocate(10, out block3); target.TryAllocate(10, out block4); target.TryAllocate(10, out block5); target.TryAllocate(target.LargestFreeBlockSize, out block6); //entire slab is now used up //Free block1 target.Free(block1); //reassign block1 to be 4 bytes; target.TryAllocate(4, out block1); //Free space should be 6 bytes now; Assert.AreEqual <long>(6, target.LargestFreeBlockSize); //Free Block2 target.Free(block2); //Free space should now be 16 bytes; Assert.AreEqual <long>(16, target.LargestFreeBlockSize); //Free Block5 target.Free(block5); //Free Block4 target.Free(block4); //Largest free space should now be 20 bytes; Assert.AreEqual <long>(20, target.LargestFreeBlockSize); //Free Block3 target.Free(block3); //Largest free space should now be 20 + 10 + 16 = 46 bytes Assert.AreEqual <long>(46, target.LargestFreeBlockSize); //Free Block6 target.Free(block6); //Largest free block should be slab size - block1 size Assert.AreEqual <long>(target.Size - block1.Length, target.LargestFreeBlockSize); //Free Block1 target.Free(block1); //Largest free block should now be slab size Assert.AreEqual <long>(target.Size, target.LargestFreeBlockSize); }
/// <summary> /// Initializes a new instance of the MemoryBlock class /// </summary> /// <param name="startLocation">Offset where memory block starts in slab</param> /// <param name="length">Length of memory block</param> /// <param name="slab">Slab to be associated with the memory block</param> internal MemoryBlock(long startLocation, long length, IMemorySlab slab) { if (startLocation < 0) { throw new ArgumentOutOfRangeException("startLocation", "StartLocation must be greater than 0"); } startLoc = startLocation; if (length <= 0) { throw new ArgumentOutOfRangeException("length", "Length must be greater than 0"); } this.length = length; if (slab == null) throw new ArgumentNullException("slab"); this.owner = slab; //TODO: If this class is converted to a struct, consider implementing IComparer, IComparable -- first figure out what sorted dictionary uses those Comparer things for }
private int singleSlabPool; //-1 or 0, used for faster access if only one slab is available /// <summary> /// Initializes a new instance of the BufferPool class /// </summary> /// <param name="slabSize">Length, in bytes, of a slab in the BufferPool</param> /// <param name="initialSlabs">Number of slabs to create initially</param> /// <param name="subsequentSlabs">Number of additional slabs to create at a time</param> public BufferPool(long slabSize, int initialSlabs, int subsequentSlabs) { if (slabSize < 1) { throw new ArgumentException("SlabSize must be equal to or greater than 1"); } if (initialSlabs < 1) { throw new ArgumentException("InitialSlabs must be equal to or greater than 1"); } if (subsequentSlabs < 1) { throw new ArgumentException("SubsequentSlabs must be equal to or greater than 1"); } this.slabSize = slabSize > MinimumSlabSize ? slabSize : MinimumSlabSize; this.initialSlabs = initialSlabs; this.subsequentSlabs = subsequentSlabs; lock (sync_slabList) { if (slabs.Count == 0) { if (initialSlabs > 1) { Interlocked.Exchange(ref singleSlabPool, 0); //false } else { Interlocked.Exchange(ref singleSlabPool, -1); //true } for (int i = 0; i < initialSlabs; i++) { slabs.Add(new MemorySlab(slabSize, this)); } firstSlab = slabs[0]; } } }
private const int MAX_SEGMENTS_PER_BUFFER = 16; //Maximum number of segments in a buffer. /// <summary> /// Initializes a new instance of the BufferPool class /// </summary> /// <param name="slabSize">Length, in bytes, of a slab in the BufferPool</param> /// <param name="initialSlabs">Number of slabs to create initially</param> /// <param name="subsequentSlabs">Number of additional slabs to create at a time</param> public BufferPool(long slabSize, int initialSlabs, int subsequentSlabs) { if (slabSize < 1) { throw new ArgumentException("slabSize must be equal to or greater than 1"); } if (initialSlabs < 1) { throw new ArgumentException("initialSlabs must be equal to or greater than 1"); } if (subsequentSlabs < 1) { throw new ArgumentException("subsequentSlabs must be equal to or greater than 1"); } if (slabSize > MaximumSlabSize) { throw new ArgumentException("slabSize cannot be larger BufferPool.MaximumSlabSize"); } this.slabSize = slabSize > MinimumSlabSize ? slabSize : MinimumSlabSize; this.initialSlabs = initialSlabs; this.subsequentSlabs = subsequentSlabs; // lock is unnecessary in this instance constructor //lock (syncSlabList) //{ if (slabs.Count == 0) { SetSingleSlabPool(initialSlabs == 1); //Assume for optimization reasons that it's a single slab pool if the number of initial slabs is 1 for (int i = 0; i < initialSlabs; i++) { slabs.Add(new MemorySlab(slabSize, this)); } firstSlab = slabs[0]; } //} }
/// <summary> /// Initializes a new instance of the MemoryBlock class /// </summary> /// <param name="startLocation">Offset where memory block starts in slab</param> /// <param name="length">Length of memory block</param> /// <param name="slab">Slab to be associated with the memory block</param> internal MemoryBlock(long startLocation, long length, IMemorySlab slab) { if (startLocation < 0) { throw new ArgumentOutOfRangeException("startLocation", "StartLocation must be greater than 0"); } startLoc = startLocation; if (length <= 0) { throw new ArgumentOutOfRangeException("length", "Length must be greater than 0"); } endLoc = startLocation + length - 1; this.length = length; if (slab == null) { throw new ArgumentNullException("slab"); } this.owner = slab; }
/// <summary> /// Initializes a new instance of the MemoryBlock class /// </summary> /// <param name="startLocation">Offset where memory block starts in slab</param> /// <param name="length">Length of memory block</param> /// <param name="slab">Slab to be associated with the memory block</param> internal MemoryBlock(long startLocation, long length, IMemorySlab slab) { if (startLocation < 0) { throw new ArgumentOutOfRangeException("startLocation", "StartLocation must be greater than 0"); } startLoc = startLocation; if (length <= 0) { throw new ArgumentOutOfRangeException("length", "Length must be greater than 0"); } this.length = length; if (slab == null) { throw new ArgumentNullException("slab"); } this.owner = slab; //TODO: If this class is converted to a struct, consider implementing IComparer, IComparable -- first figure out what sorted dictionary uses those Comparer things for }
private static ManagedBuffer GetNewBuffer(IMemorySlab Slab) { IMemoryBlock allocatedMemoryBlock; Slab.TryAllocate(blockSize, out allocatedMemoryBlock); ManagedBuffer target = new ManagedBuffer(allocatedMemoryBlock); return target; }
public void BadConstructionTest2() { IMemorySlab target = CreateInvalidMemorySlab2(); }
/// <summary> /// Creates a buffer of the specified size, filled with the contents of a specified byte array /// </summary> /// <param name="size">Buffer size, in bytes</param> /// <param name="filledWith">Byte array to copy to buffer</param> /// <returns>IBuffer object of requested size</returns> public IBuffer GetBuffer(long size, byte[] filledWith) { if (size < 0) { throw new ArgumentException("size must be greater than 0"); } //TODO: If size is larger than 16 * SlabSize (or MaxNumberOfSegments * SlabSize) then throw exception saying you can't have a buffer greater than 16 times Slab size //Write test for this //Make sure filledWith can fit into the requested buffer, so that we do not allocate a buffer and then //an exception is thrown (when IBuffer.FillWith() is called) before the buffer is returned. if (filledWith != null) { if (filledWith.LongLength > size) { throw new ArgumentException("Length of filledWith array cannot be larger than desired buffer size"); } if (filledWith.LongLength == 0) { filledWith = null; } //TODO: Write test that will test that IBuffer.FillWith() doesn't throw an exception (and that buffers aren't allocated) in this method } if (size == 0) { //Return an empty buffer return(new ManagedBuffer(firstSlab)); } List <IMemoryBlock> allocatedBlocks = new List <IMemoryBlock>(); //TODO: Consider the performance penalty involved in making the try-catch below a constrained region //RuntimeHelpers.PrepareConstrainedRegions(); try { IMemorySlab[] slabArr; long allocdLengthTally = 0; if (GetSingleSlabPool()) { //Optimization: Chances are that there'll be just one slab in a pool, so access it directly //and avoid the lock statement involved while creating an array of slabs. //Note that even if singleSlabPool is inaccurate, this method will still work properly. //The optimization is effective because singleSlabPool will be accurate majority of the time. slabArr = new IMemorySlab[] { firstSlab }; allocdLengthTally = TryAllocateBlocksInSlabs(size, MAX_SEGMENTS_PER_BUFFER, slabArr, ref allocatedBlocks); if (allocdLengthTally == size) { //We got the entire length we are looking for, so leave return(GetFilledBuffer(allocatedBlocks, filledWith)); } SetSingleSlabPool(false); // Slab count will soon be incremented } else { lock (syncSlabList) { slabArr = slabs.ToArray(); } allocdLengthTally = TryAllocateBlocksInSlabs(size, MAX_SEGMENTS_PER_BUFFER, slabArr, ref allocatedBlocks); if (allocdLengthTally == size) { //We got the entire length we are looking for, so leave return(GetFilledBuffer(allocatedBlocks, filledWith)); } } //Try to create new slab lock (syncNewSlab) { //Look again for free block lock (syncSlabList) { slabArr = slabs.ToArray(); } allocdLengthTally += TryAllocateBlocksInSlabs(size - allocdLengthTally, MAX_SEGMENTS_PER_BUFFER - allocatedBlocks.Count, slabArr, ref allocatedBlocks); if (allocdLengthTally == size) { //found it -- leave return(GetFilledBuffer(allocatedBlocks, filledWith)); } List <IMemorySlab> newSlabList = new List <IMemorySlab>(); do { //Unable to find available free space, so create new slab MemorySlab newSlab = new MemorySlab(slabSize, this); IMemoryBlock allocdBlk; if (slabSize > size - allocdLengthTally) { //Allocate remnant newSlab.TryAllocate(size - allocdLengthTally, out allocdBlk); } else { //Allocate entire slab newSlab.TryAllocate(slabSize, out allocdBlk); } newSlabList.Add(newSlab); allocatedBlocks.Add(allocdBlk); allocdLengthTally += allocdBlk.Length; }while (allocdLengthTally < size); lock (syncSlabList) { //Add new slabs to collection slabs.AddRange(newSlabList); //Add extra slabs as requested in object properties for (int i = 0; i < subsequentSlabs - 1; i++) { slabs.Add(new MemorySlab(slabSize, this)); } } } return(GetFilledBuffer(allocatedBlocks, filledWith)); } catch { //OOM, Thread abort exceptions and other ugly things can happen so roll back any allocated blocks. //This will prevent a limbo situation where those blocks are allocated but caller is unaware and can't deallocate them. //NOTE: This try-catch block should not be within a lock as it calls MemorySlab.Free which takes locks, //and in turn calls BufferPool.TryFreeSlabs which takes other locks and can lead to a dead-lock/race condition. //TODO: Write rollback test. for (int b = 0; b < allocatedBlocks.Count; b++) { allocatedBlocks[b].Slab.Free(allocatedBlocks[b]); } throw; } }
/// <summary> /// Helper method that searches for free block in an array of slabs and returns the allocated block /// </summary> /// <param name="length">Requested length of memory block</param> /// <param name="slabs">Array of slabs to search</param> /// <param name="allocatedBlock">Allocated memory block</param> /// <returns>True if memory block was successfully allocated. False, if otherwise</returns> private static bool TryAllocateBlockInSlabs(long length, IMemorySlab[] slabs, out IMemoryBlock allocatedBlock) { allocatedBlock = null; for (int i = 0; i < slabs.Length; i++) { if (slabs[i].LargestFreeBlockSize >= length) { if (slabs[i].TryAllocate(length, out allocatedBlock)) { return true; } } } return false; }
/// <summary> /// Creates a buffer of the specified size /// </summary> /// <param name="size">Buffer size, in bytes</param> /// <returns>IBuffer object of requested size</returns> public IBuffer GetBuffer(long size) { if (size < 0) throw new ArgumentException("Length must be greater than 0"); if (size == 0) return new ManagedBuffer(firstSlab); //Return an empty buffer IMemoryBlock allocatedBlock; IMemorySlab[] slabArr; if (singleSlabPool == -1) { //Optimization: Chances are that there'll be just one slab in a pool, so access it directly //and avoid the lock statement involved while creating an array of slabs. //Note that even if singleSlabPool is inaccurate, this method will still work properly. //The optimization is effective because singleSlabPool will be accurate majority of the time. slabArr = new IMemorySlab[] { firstSlab }; if (TryAllocateBlockInSlabs(size, slabArr, out allocatedBlock)) { return new ManagedBuffer(allocatedBlock); } Interlocked.Exchange(ref singleSlabPool, 0); // Slab count will soon be incremented } else { lock (sync_slabList) { slabArr = slabs.ToArray(); } if (TryAllocateBlockInSlabs(size, slabArr, out allocatedBlock)) { return new ManagedBuffer(allocatedBlock); } } lock (sync_newSlab) { //Look again for free block lock (sync_slabList) { slabArr = slabs.ToArray(); } if (TryAllocateBlockInSlabs(size, slabArr, out allocatedBlock)) { //found it -- leave return new ManagedBuffer(allocatedBlock); } //Unable to find available free space, so create new slab MemorySlab newSlab = new MemorySlab(slabSize, this); newSlab.TryAllocate(size, out allocatedBlock); lock (sync_slabList) { //Add new Slab to collection slabs.Add(newSlab); //Add extra slabs as requested in object properties for (int i = 0; i < subsequentSlabs - 1; i++) { slabs.Add(new MemorySlab(slabSize, this)); } } } return new ManagedBuffer(allocatedBlock); }
//Gets a multi slab buffer private static ManagedBuffer GetNewBuffer(IMemorySlab[] Slabs) { List<IMemoryBlock> blockList = new List<IMemoryBlock>(); IMemoryBlock allocatedMemoryBlock; foreach (var slab in Slabs) { slab.TryAllocate(blockSize, out allocatedMemoryBlock); blockList.Add(allocatedMemoryBlock); } ManagedBuffer target = new ManagedBuffer(blockList); return target; }
/// <summary> /// Creates a buffer of the specified size /// </summary> /// <param name="size">Buffer size, in bytes</param> /// <returns>IBuffer object of requested size</returns> public IBuffer GetBuffer(long size) { if (size < 0) { throw new ArgumentException("Length must be greater than 0"); } if (size == 0) { return(new ManagedBuffer(firstSlab)); //Return an empty buffer } IMemoryBlock allocatedBlock; IMemorySlab[] slabArr; if (singleSlabPool == -1) { //Optimization: Chances are that there'll be just one slab in a pool, so access it directly //and avoid the lock statement involved while creating an array of slabs. //Note that even if singleSlabPool is inaccurate, this method will still work properly. //The optimization is effective because singleSlabPool will be accurate majority of the time. slabArr = new IMemorySlab[] { firstSlab }; if (TryAllocateBlockInSlabs(size, slabArr, out allocatedBlock)) { return(new ManagedBuffer(allocatedBlock)); } Interlocked.Exchange(ref singleSlabPool, 0); // Slab count will soon be incremented } else { lock (sync_slabList) { slabArr = slabs.ToArray(); } if (TryAllocateBlockInSlabs(size, slabArr, out allocatedBlock)) { return(new ManagedBuffer(allocatedBlock)); } } lock (sync_newSlab) { //Look again for free block lock (sync_slabList) { slabArr = slabs.ToArray(); } if (TryAllocateBlockInSlabs(size, slabArr, out allocatedBlock)) { //found it -- leave return(new ManagedBuffer(allocatedBlock)); } //Unable to find available free space, so create new slab MemorySlab newSlab = new MemorySlab(slabSize, this); newSlab.TryAllocate(size, out allocatedBlock); lock (sync_slabList) { //Add new Slab to collection slabs.Add(newSlab); //Add extra slabs as requested in object properties for (int i = 0; i < subsequentSlabs - 1; i++) { slabs.Add(new MemorySlab(slabSize, this)); } } } return(new ManagedBuffer(allocatedBlock)); }
private const int MAX_SEGMENTS_PER_BUFFER = 16; //Maximum number of segments in a buffer. /// <summary> /// Initializes a new instance of the BufferPool class /// </summary> /// <param name="slabSize">Length, in bytes, of a slab in the BufferPool</param> /// <param name="initialSlabs">Number of slabs to create initially</param> /// <param name="subsequentSlabs">Number of additional slabs to create at a time</param> public BufferPool(long slabSize, int initialSlabs, int subsequentSlabs) { if (slabSize < 1) throw new ArgumentException("slabSize must be equal to or greater than 1"); if (initialSlabs < 1) throw new ArgumentException("initialSlabs must be equal to or greater than 1"); if (subsequentSlabs < 1) throw new ArgumentException("subsequentSlabs must be equal to or greater than 1"); if (slabSize > MaximumSlabSize) throw new ArgumentException("slabSize cannot be larger BufferPool.MaximumSlabSize"); this.slabSize = slabSize > MinimumSlabSize ? slabSize : MinimumSlabSize; this.initialSlabs = initialSlabs; this.subsequentSlabs = subsequentSlabs; // lock is unnecessary in this instance constructor //lock (syncSlabList) //{ if (slabs.Count == 0) { SetSingleSlabPool(initialSlabs == 1); //Assume for optimization reasons that it's a single slab pool if the number of initial slabs is 1 for (int i = 0; i < initialSlabs; i++) { slabs.Add(new MemorySlab(slabSize, this)); } firstSlab = slabs[0]; } //} }
/// <summary> /// Creates a buffer of the specified size, filled with the contents of a specified byte array /// </summary> /// <param name="size">Buffer size, in bytes</param> /// <param name="filledWith">Byte array to copy to buffer</param> /// <returns>IBuffer object of requested size</returns> public IBuffer GetBuffer(long size, byte[] filledWith) { if (size < 0) throw new ArgumentException("size must be greater than 0"); //TODO: If size is larger than 16 * SlabSize (or MaxNumberOfSegments * SlabSize) then throw exception saying you can't have a buffer greater than 16 times Slab size //Write test for this //Make sure filledWith can fit into the requested buffer, so that we do not allocate a buffer and then //an exception is thrown (when IBuffer.FillWith() is called) before the buffer is returned. if (filledWith != null) { if (filledWith.LongLength > size) throw new ArgumentException("Length of filledWith array cannot be larger than desired buffer size"); if (filledWith.LongLength == 0) filledWith = null; //TODO: Write test that will test that IBuffer.FillWith() doesn't throw an exception (and that buffers aren't allocated) in this method } if (size == 0) { //Return an empty buffer return new ManagedBuffer(firstSlab); } List<IMemoryBlock> allocatedBlocks = new List<IMemoryBlock>(); //TODO: Consider the performance penalty involved in making the try-catch below a constrained region //RuntimeHelpers.PrepareConstrainedRegions(); try { IMemorySlab[] slabArr; long allocdLengthTally = 0; if (GetSingleSlabPool()) { //Optimization: Chances are that there'll be just one slab in a pool, so access it directly //and avoid the lock statement involved while creating an array of slabs. //Note that even if singleSlabPool is inaccurate, this method will still work properly. //The optimization is effective because singleSlabPool will be accurate majority of the time. slabArr = new IMemorySlab[] { firstSlab }; allocdLengthTally = TryAllocateBlocksInSlabs(size, MAX_SEGMENTS_PER_BUFFER, slabArr, ref allocatedBlocks); if (allocdLengthTally == size) { //We got the entire length we are looking for, so leave return GetFilledBuffer(allocatedBlocks, filledWith); } SetSingleSlabPool(false); // Slab count will soon be incremented } else { lock (syncSlabList) { slabArr = slabs.ToArray(); } allocdLengthTally = TryAllocateBlocksInSlabs(size, MAX_SEGMENTS_PER_BUFFER, slabArr, ref allocatedBlocks); if (allocdLengthTally == size) { //We got the entire length we are looking for, so leave return GetFilledBuffer(allocatedBlocks, filledWith); } } //Try to create new slab lock (syncNewSlab) { //Look again for free block lock (syncSlabList) { slabArr = slabs.ToArray(); } allocdLengthTally += TryAllocateBlocksInSlabs(size - allocdLengthTally, MAX_SEGMENTS_PER_BUFFER - allocatedBlocks.Count, slabArr, ref allocatedBlocks); if (allocdLengthTally == size) { //found it -- leave return GetFilledBuffer(allocatedBlocks, filledWith); } List<IMemorySlab> newSlabList = new List<IMemorySlab>(); do { //Unable to find available free space, so create new slab MemorySlab newSlab = new MemorySlab(slabSize, this); IMemoryBlock allocdBlk; if (slabSize > size - allocdLengthTally) { //Allocate remnant newSlab.TryAllocate(size - allocdLengthTally, out allocdBlk); } else { //Allocate entire slab newSlab.TryAllocate(slabSize, out allocdBlk); } newSlabList.Add(newSlab); allocatedBlocks.Add(allocdBlk); allocdLengthTally += allocdBlk.Length; } while (allocdLengthTally < size); lock (syncSlabList) { //Add new slabs to collection slabs.AddRange(newSlabList); //Add extra slabs as requested in object properties for (int i = 0; i < subsequentSlabs - 1; i++) { slabs.Add(new MemorySlab(slabSize, this)); } } } return GetFilledBuffer(allocatedBlocks, filledWith); } catch { //OOM, Thread abort exceptions and other ugly things can happen so roll back any allocated blocks. //This will prevent a limbo situation where those blocks are allocated but caller is unaware and can't deallocate them. //NOTE: This try-catch block should not be within a lock as it calls MemorySlab.Free which takes locks, //and in turn calls BufferPool.TryFreeSlabs which takes other locks and can lead to a dead-lock/race condition. //TODO: Write rollback test. for (int b = 0; b < allocatedBlocks.Count; b++) { allocatedBlocks[b].Slab.Free(allocatedBlocks[b]); } throw; } }
/// <summary> /// Helper method that searches for free blocks in an array of slabs and returns allocated blocks /// </summary> /// <param name="totalLength">Requested total length of all memory blocks</param> /// <param name="maxBlocks">Maximum number of memory blocks to allocate</param> /// <param name="slabs">Array of slabs to search</param> /// <param name="allocatedBlocks">List of allocated memory block</param> /// <returns>True if memory block was successfully allocated. False, if otherwise</returns> private static long TryAllocateBlocksInSlabs(long totalLength, int maxBlocks, IMemorySlab[] slabs, ref List<IMemoryBlock> allocatedBlocks) { allocatedBlocks = new List<IMemoryBlock>(); long minBlockSize; long allocatedSizeTally = 0; long largest; long reqLength; IMemoryBlock allocdBlock; int allocdCount = 0; //TODO: Figure out how to do this math without involving floating point arithmetic minBlockSize = (long)Math.Ceiling(totalLength / (float)maxBlocks); do { allocdBlock = null; for (int i = 0; i < slabs.Length; i++) { largest = slabs[i].LargestFreeBlockSize; if (largest >= minBlockSize) { //Figure out what length to request for reqLength = slabs[i].TryAllocate(minBlockSize, totalLength - allocatedSizeTally, out allocdBlock); if (reqLength > 0) { allocatedBlocks.Add(allocdBlock); allocatedSizeTally += reqLength; allocdCount++; if (allocatedSizeTally == totalLength) return allocatedSizeTally; //Calculate the new minimum block size //TODO: Figure out how to do this math without involving floating point arithmetic minBlockSize = (long)Math.Ceiling((totalLength - allocatedSizeTally) / (float)(maxBlocks - allocdCount)); //Scan again from start because there is a chance the smaller minimum block size exists in previously skipped slabs break; } } } } while (allocdBlock != null); return allocatedSizeTally; }