Search code examples
c#design-patternspooling

C# Object Pooling Pattern implementation


Does anyone have a good resource on implementing a shared object pool strategy for a limited resource in vein of Sql connection pooling? (ie would be implemented fully that it is thread safe).

To follow up in regards to @Aaronaught request for clarification the pool usage would be for load balancing requests to an external service. To put it in a scenario that would probably be easier to immediately understand as opposed to my direct situtation. I have a session object that functions similarly to the ISession object from NHibernate. That each unique session manages it's connection to the database. Currently I have 1 long running session object and am encountering issues where my service provider is rate limiting my usage of this individual session.

Due to their lack of expectation that a single session would be treated as a long running service account they apparently treat it as a client that is hammering their service. Which brings me to my question here, instead of having 1 individual session I would create a pool of different sessions and split the requests up to the service across those multiple sessions instead of creating a single focal point as I was previously doing.

Hopefully that background offers some value but to directly answer some of your questions:

Q: Are the objects expensive to create?
A: No objects are a pool of limited resources

Q: Will they be acquired/released very frequently?
A: Yes, once again they can be thought of NHibernate ISessions where 1 is usually acquired and released for the duration of every single page request.

Q: Will a simple first-come-first-serve suffice or do you need something more intelligent, i.e. that would prevent starvation?
A: A simple round robin type distribution would suffice, by starvation I assume you mean if there are no available sessions that callers become blocked waiting for releases. This isn't really applicable since the sessions can be shared by different callers. My goal is distribute the usage across multiple sessions as opposed to 1 single session.

I believe this is probably a divergence from a normal usage of an object pool which is why I originally left this part out and planned just to adapt the pattern to allow sharing of objects as opposed to allowing a starvation situation to ever occur.

Q: What about things like priorities, lazy vs. eager loading, etc.?
A: There is no prioritization involved, for simplicity's sake just assume that I would create the pool of available objects at the creation of the pool itself.


Solution

  • Object Pooling in .NET Core

    The dotnet core has an implementation of object pooling added to the base class library (BCL). You can read the original GitHub issue here and view the code for System.Buffers. Currently the ArrayPool is the only type available and is used to pool arrays. There is a nice blog post here.

    namespace System.Buffers
    {
        public abstract class ArrayPool<T>
        {
            public static ArrayPool<T> Shared { get; internal set; }
    
            public static ArrayPool<T> Create(int maxBufferSize = <number>, int numberOfBuffers = <number>);
    
            public T[] Rent(int size);
    
            public T[] Enlarge(T[] buffer, int newSize, bool clearBuffer = false);
    
            public void Return(T[] buffer, bool clearBuffer = false);
        }
    }
    

    An example of its usage can be seen in ASP.NET Core. Because it is in the dotnet core BCL, ASP.NET Core can share it's object pool with other objects such as Newtonsoft.Json's JSON serializer. You can read this blog post for more information on how Newtonsoft.Json is doing this.

    Object Pooling in Microsoft Roslyn C# Compiler

    The new Microsoft Roslyn C# compiler contains the ObjectPool type, which is used to pool frequently used objects which would normally get new'ed up and garbage collected very often. This reduces the amount and size of garbage collection operations which have to happen. There are a few different sub-implementations all using ObjectPool (See: Why are there so many implementations of Object Pooling in Roslyn?).

    1 - SharedPools - Stores a pool of 20 objects or 100 if the BigDefault is used.

    // Example 1 - In a using statement, so the object gets freed at the end.
    using (PooledObject<Foo> pooledObject = SharedPools.Default<List<Foo>>().GetPooledObject())
    {
        // Do something with pooledObject.Object
    }
    
    // Example 2 - No using statement so you need to be sure no exceptions are not thrown.
    List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
    // Do something with list
    SharedPools.Default<List<Foo>>().Free(list);
    
    // Example 3 - I have also seen this variation of the above pattern, which ends up the same as Example 1, except Example 1 seems to create a new instance of the IDisposable [PooledObject<T>][4] object. This is probably the preferred option if you want fewer GC's.
    List<Foo> list = SharedPools.Default<List<Foo>>().AllocateAndClear();
    try
    {
        // Do something with list
    }
    finally
    {
        SharedPools.Default<List<Foo>>().Free(list);
    }
    

    2 - ListPool and StringBuilderPool - Not strictly separate implementations but wrappers around the SharedPools implementation shown above specifically for List and StringBuilder's. So this re-uses the pool of objects stored in SharedPools.

    // Example 1 - No using statement so you need to be sure no exceptions are thrown.
    StringBuilder stringBuilder= StringBuilderPool.Allocate();
    // Do something with stringBuilder
    StringBuilderPool.Free(stringBuilder);
    
    // Example 2 - Safer version of Example 1.
    StringBuilder stringBuilder= StringBuilderPool.Allocate();
    try
    {
        // Do something with stringBuilder
    }
    finally
    {
        StringBuilderPool.Free(stringBuilder);
    }
    

    3 - PooledDictionary and PooledHashSet - These use ObjectPool directly and have a totally separate pool of objects. Stores a pool of 128 objects.

    // Example 1
    PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
    // Do something with hashSet.
    hashSet.Free();
    
    // Example 2 - Safer version of Example 1.
    PooledHashSet<Foo> hashSet = PooledHashSet<Foo>.GetInstance()
    try
    {
        // Do something with hashSet.
    }
    finally
    {
        hashSet.Free();
    }
    

    Microsoft.IO.RecyclableMemoryStream

    This library provides pooling for MemoryStream objects. It's a drop-in replacement for System.IO.MemoryStream. It has exactly the same semantics. It was designed by Bing engineers. Read the blog post here or see the code on GitHub.

    var sourceBuffer = new byte[]{0,1,2,3,4,5,6,7}; 
    var manager = new RecyclableMemoryStreamManager(); 
    using (var stream = manager.GetStream()) 
    { 
        stream.Write(sourceBuffer, 0, sourceBuffer.Length); 
    }
    

    Note that RecyclableMemoryStreamManager should be declared once and it will live for the entire process–this is the pool. It is perfectly fine to use multiple pools if you desire.