Wednesday, August 10, 2011

Generic C# Resource Pool

I guess this becomes another almost code-only blog.

Definition

A resource pool is a class that manages a synchronized access of (theoretically) infinite callers of a restricted count of resource items.



One reason to use a resource pool could be a integrated sub system that is restricted by its maximum count of concurrent accessing threads. This could be a web service with a restricted count of sessions. Another reason for a resource pool is a resource that becomes working slow when it becomes accessed by to many concurrent connections. This could be a web-service, a database or a farm of processing servers. A third reason for a resource pool could be a resource that takes long to become initialized, but can be reused. This could be database connections, XSLT transformation classes, custom XML serializers or many other classes.
Some well known .NET framework implementations of resource pools are:

Implementation

A few month ago I wrote myself a generic resource pool class that helps me to get those kinds of bottle necks managed.

Here is my implementation of a generic, thread safe C# resource pool. All source code files, as well as some NUnit tests, are also attached as downloadable files at bottom of this post.

// ===================================================================
// PoolItem<T>
// ===================================================================

/// <summary>
/// Represents an item in the <see cref="ResourcePool{T}"/>
/// </summary>
/// <typeparam name="T">The type of the resource to be hold</typeparam>
public sealed class PoolItem<T> : IDisposable where T : class {
   internal PoolItem(ResourcePool<T> pool, T resource) {
      _pool = pool;
      _resource = resource;
   }

   private T _resource;
   private readonly ResourcePool<T> _pool;

   public static implicit operator T(PoolItem<T> item) {
      return item.Resource;
   }

   /// <summary>
   /// Gets the resource hold by this resource pool item
   /// </summary>
   public T Resource { get { return _resource; } }

   /// <summary>
   /// Disposes this instance of an resource pool item and sends the resource back
   /// to the pool
   /// </summary>
   public void Dispose() {
      _pool.SendBackToPool(_resource);
      _resource = null;
   }
}


// ===================================================================
// ResourcePool<T>
// ===================================================================

/// <summary>
/// Generic pool class
/// </summary>
/// <typeparam name="T">The type of items to be stored in the pool</typeparam>
public class ResourcePool<T> : IDisposable where T : class {
   /// <summary>
   /// Creates a new pool
   /// </summary>
   /// <param name="factory">The factory method to create new items to
   ///  be stored in the pool</param>
   public ResourcePool(Func<ResourcePool<T>, T> factory) {
      if (factory == null)
         throw new ArgumentNullException("factory");
      _factoryMethod = factory;
   }

   private readonly Func<ResourcePool<T>, T> _factoryMethod;
   private ConcurrentQueue<PoolItem<T>> _freeItems = 
      new ConcurrentQueue<PoolItem<T>>();
   private ConcurrentQueue<AutoResetEvent> _waitLocks = 
      new ConcurrentQueue<AutoResetEvent>();
   private ConcurrentDictionary<AutoResetEvent, PoolItem<T>> _syncContext =
      new ConcurrentDictionary<AutoResetEvent, PoolItem<T>>();

   public Action<T> CleanupPoolItem { get; set; }

   /// <summary>
   /// Gets the current count of items in the pool
   /// </summary>
   public int Count { get; private set; }

   public void Dispose() {
      lock (this) {
         if (Count != _freeItems.Count)
            throw new InvalidOperationException(
               "Cannot dispose the resource pool while one or more pooled "
               + "items are in use");

         foreach (var poolItem in _freeItems) {
            Action<T> cleanMethod = CleanupPoolItem;
            if (cleanMethod != null)
               CleanupPoolItem(poolItem.Resource);
         }

         Count = 0;
         _freeItems = null;
         _waitLocks = null;
         _syncContext = null;
      }
   }

   /// <summary>
   /// Gets a free resource from the pool. If no free items available this method 
   /// tries to create a new item. If no new item could be created this method 
   /// waits until another thread frees one resource.
   /// </summary>
   /// <returns>A resource item</returns>
   public PoolItem<T> GetItem() {
      PoolItem<T> item;

      // try to get an item
      if (!TryGetItem(out item)) {
         AutoResetEvent waitLock = null;

         lock (this) {
            // try to get an entry in exclusive mode
            if (!TryGetItem(out item)) {
               // no item available, create a wait lock and enqueue it
               waitLock = new AutoResetEvent(false);
               _waitLocks.Enqueue(waitLock);
            }
         }

         if (waitLock != null) {
            // wait until a new item is available
            waitLock.WaitOne();
            _syncContext.TryRemove(waitLock, out item);
            waitLock.Dispose();
         }
      }

      return item;
   }

   private bool TryGetItem(out PoolItem<T> item) {
      // try to get an already pooled resource
      if (_freeItems.TryDequeue(out item))
         return true;

      lock (this) {
         // try to create a new resource
         T resource = _factoryMethod(this);
         if (resource == null && Count == 0)
            throw new InvalidOperationException("Pool empty and no item created");

         if (resource != null) {
            // a new resource was created and can be returned
            Count++;
            item = new PoolItem<T>(this, resource);
         }
         else {
            // no items available to return at the moment
            item = null;
         }

         return item != null;
      }
   }

   /// <summary>
   /// Called from <see cref="PoolItem{T}"/> to free previously taked resources
   /// </summary>
   /// <param name="resource">The resource to send back into the pool.</param>
   internal void SendBackToPool(T resource) {
      lock (this) {
         PoolItem<T> item = new PoolItem<T>(this, resource);
         AutoResetEvent waitLock;

         if (_waitLocks.TryDequeue(out waitLock)) {
            _syncContext.TryAdd(waitLock, item);
            waitLock.Set();
         }
         else {
            _freeItems.Enqueue(item);
         }
      }
   }
}

As you see, the ResourcePool<T> returns PoolItem<T> objects that hold a reference of the pooled resources. The pool does not need to become initialized with any resource items, new resource items become lazy initialized when (if) needed. Therefore the pool requires a factory method provided at construction. This method becomes called whenever a new resource is requested and no free resources are currently available. If the factory method returns null the requesting thread becomes suspended until another thread releases a used resource.

Usage

The pool can be used out of the box.
class MyResource {
   public void DoSomething() { }
}

[Test]
public void SampleUsage() {
   ResourcePool<MyResource> pool = new ResourcePool<MyResource>(CreateResource);

   using (var poolItem = pool.GetItem()) {
      MyResource resource = poolItem.Resource;
      resource.DoSomething();
   }
}

private static MyResource CreateResource(ResourcePool<MyResource> pool) {
   return pool.Count < 3 ? new MyResource() : null;
}

However, if used at many positions in your system I'd suggest to wrap it into a custom pool class. This pool can return a wrapper of the real resources that provide a more specific interface to the consumer. This wrapper can hold an internal reference to a PoolItem<T> and implement IDisposable to free the resources back into the pool when not needed anymore.
// ==========================================================
// The real resource
class InternalResource {
   public void DoSomething() { }
}

// ==========================================================
// The external wrapper returned to the consumer
class MyResource : IDisposable {
   private PoolItem<InternalResource> _poolItem;

   public MyResource(PoolItem<InternalResource> poolItem) {
      _poolItem = poolItem;
   }

   public void DoSomething() {
      _poolItem.Resource.DoSomething();
   }

   public void Dispose() {
      _poolItem.Dispose();
      _poolItem = null;
   }
}

// ==========================================================
// The custom pool
class MyPool {
   private ResourcePool<InternalResource> _pool;

   public MyPool() {
      _pool = new ResourcePool<InternalResource>(CreateResource);
   }

   public MyResource GetItem() {
      return new MyResource(_pool.GetItem());
   }

   private InternalResource CreateResource(ResourcePool<InternalResource> pool) {
      return pool.Count < 3 ? new InternalResource() : null;
   }
}

// ==========================================================
// Sample usage
[Test]
public void SampleUsage() {
   MyPool pool = new MyPool();

   using (MyResource resource = pool.GetItem()) {
      resource.DoSomething();
   }
}

Here are the files for download:

I hope the pool can help you to solve a few of your issues, as it did to me.

1 comment:

  1. This is an Excellent implementation and has really helped us get over the connection issues .

    ReplyDelete