diff --git a/src/NetMQ.Tests/BufferPoolTests.cs b/src/NetMQ.Tests/BufferPoolTests.cs new file mode 100644 index 00000000..3ece815c --- /dev/null +++ b/src/NetMQ.Tests/BufferPoolTests.cs @@ -0,0 +1,70 @@ +// See BufferPool.cs for explanation of USE_SERVICE_MODEL define. + +// #define USE_SERVICE_MODEL +using System; +using Xunit; + +namespace NetMQ.Tests +{ + public abstract class BufferPoolTests + { + protected int maxBufferSize = 2; + protected long maxBufferPoolSize = 100L; + // Silence the non-nullable warning. +#pragma warning disable 8618 + protected IBufferPool pool; +#pragma warning restore 8618 + + [Theory] + [InlineData(500)] + [InlineData(50000)] + [InlineData(5000000)] + [InlineData(500000000)] + public void Rent(int size) + { + /* The pool sizes were chosen to be very small. It's clear they do + * not constitute any bounds on the array sizes that can be + * requested. BufferManagerBufferPool has the same behavior + * though. */ + var array = pool.Take(size); + Assert.Equal(size, array.Length); + } + + // I was not able to provoke this to happen. Maybe with a long. + // [Fact] + // public void RentTooBigBuffer() + // { + // Assert.ThrowsAny(() => pool.Take(900000000)); + // } + + [Fact] + public void Return() { + var array = pool.Take(10); + pool.Return(array); + } + + [Fact] + public void ReturnUnknown() { + var array = new byte[100]; + Assert.Throws(() => pool.Return(array)); + } + } + + public class ArrayBufferPoolTests : BufferPoolTests + { + public ArrayBufferPoolTests() + { + pool = new ArrayBufferPool(maxBufferPoolSize, maxBufferSize); + } + } + +#if USE_SERVICE_MODEL + public class BufferManagerBufferPoolTests : BufferPoolTests + { + public BufferManagerBufferPoolTests() + { + pool = new BufferManagerBufferPool(maxBufferPoolSize, maxBufferSize); + } + } +#endif +} diff --git a/src/NetMQ/BufferPool.cs b/src/NetMQ/BufferPool.cs index dea5e6eb..ccc40369 100644 --- a/src/NetMQ/BufferPool.cs +++ b/src/NetMQ/BufferPool.cs @@ -1,6 +1,15 @@ +// USE_SERVICE_MODEL requires the System.ServiceModel.Primitives package. +// It has been substituted by the System.Buffers package. This define remains +// merely to be able to exercise tests against both IBufferPool implementations. + +// #define USE_SERVICE_MODEL using System; +#if USE_SERVICE_MODEL using System.ServiceModel.Channels; +#endif +using System.Collections.Generic; using System.Threading; +using System.Buffers; namespace NetMQ { @@ -24,6 +33,81 @@ public interface IBufferPool : IDisposable void Return(byte[] buffer); } + /// + /// This implementation of uses System.Buffer's ArrayPool + /// class to manage a pool of buffers. + /// + public class ArrayBufferPool : IBufferPool + { + private readonly ArrayPool m_arrayPool; + private readonly HashSet m_onLoan; + + /// + /// Create a new ArrayBufferPool with the specified maximum buffer pool size + /// and a maximum size for each individual buffer in the pool. + /// + /// the maximum size to allow for the buffer pool + /// the maximum size to allow for each individual buffer in the pool + /// There was insufficient memory to create the requested buffer pool. + /// Either maxBufferPoolSize or maxBufferSize was less than zero. + public ArrayBufferPool(long maxBufferPoolSize, int maxBufferSize) + { + m_arrayPool = ArrayPool.Create(maxBufferSize, (int) maxBufferPoolSize); + m_onLoan = new HashSet(); + } + + /// + /// Return a byte-array buffer of at least the specified size from the pool. + /// + /// the size in bytes of the requested buffer + /// a byte-array that is the requested size + /// size cannot be less than zero + public byte[] Take(int size) + { + var loaner = m_arrayPool.Rent(size); + m_onLoan.Add(loaner); + return loaner; + } + + /// + /// Return the given buffer to this manager pool. + /// + /// a reference to the buffer being returned + /// the Length of buffer does not match the pool's buffer length property + /// the buffer reference cannot be null + public void Return(byte[] buffer) + { + if (m_onLoan.Remove(buffer)) + m_arrayPool.Return(buffer); + else + throw new ArgumentException("Buffer returned to pool was not rented.", + nameof(buffer)); + } + + /// + /// Release the buffers currently cached in this manager. + /// + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + /// + /// Release the buffers currently cached in this manager. + /// + /// true if managed resources are to be disposed + protected virtual void Dispose(bool disposing) + { + if (!disposing) + return; + foreach (var loaner in m_onLoan) + Return(loaner); + m_onLoan.Clear(); + } + } + +#if USE_SERVICE_MODEL /// /// This implementation of uses WCF's /// class to manage a pool of buffers. @@ -88,6 +172,7 @@ protected virtual void Dispose(bool disposing) m_bufferManager.Clear(); } } +#endif /// /// This simple implementation of does no buffer pooling. Instead, it uses regular @@ -134,6 +219,7 @@ protected virtual void Dispose(bool disposing) } } + /// /// Contains a singleton instance of used for allocating byte arrays /// for instances with type . @@ -144,7 +230,7 @@ protected virtual void Dispose(bool disposing) /// /// The default implementation is . /// - /// Call to replace it with a . + /// Call to replace it with a pooling implementation. /// Call to reinstate the default . /// Call to substitute a custom implementation for the allocation and /// deallocation of message buffers. @@ -163,7 +249,7 @@ public static void SetGCBufferPool() } /// - /// Set BufferPool to use the to manage the buffer-pool. + /// Set BufferPool to use a buffer pooling implementation to manage the buffer-pool. /// /// the maximum size to allow for the buffer pool /// the maximum size to allow for each individual buffer in the pool @@ -171,7 +257,11 @@ public static void SetGCBufferPool() /// Either maxBufferPoolSize or maxBufferSize was less than zero. public static void SetBufferManagerBufferPool(long maxBufferPoolSize, int maxBufferSize) { +#if USE_SERVICE_MODEL SetCustomBufferPool(new BufferManagerBufferPool(maxBufferPoolSize, maxBufferSize)); +#else + SetCustomBufferPool(new ArrayBufferPool(maxBufferPoolSize, maxBufferSize)); +#endif } /// diff --git a/src/NetMQ/NetMQ.csproj b/src/NetMQ/NetMQ.csproj index 71422794..f06e4847 100644 --- a/src/NetMQ/NetMQ.csproj +++ b/src/NetMQ/NetMQ.csproj @@ -23,7 +23,7 @@ snupkg true - + true @@ -37,16 +37,15 @@ + - -