Skip to content

Instantly share code, notes, and snippets.

@drub0y
Created May 30, 2013 21:43
Show Gist options
  • Save drub0y/5681501 to your computer and use it in GitHub Desktop.
Save drub0y/5681501 to your computer and use it in GitHub Desktop.
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace HackedBrain.Utilities
{
public sealed class ObjectPool<T> where T : class, new()
{
#region Fields
private static readonly TraceSource TraceSource = new TraceSource("HackedBrain.Utilities.ObjectPool", SourceLevels.Warning);
private static readonly SourceSwitch TraceSwitch = ObjectPool<T>.TraceSource.Switch;
private BlockingCollection<ObjectPoolEntry<T>> innerPool;
private Func<T> itemFactory;
private Action<T> itemCleanup;
private int minPoolSize;
private int maxPoolSize;
private TimeSpan maxItemAge;
private int instancesBeingCreated;
private int outstandingLeases;
#endregion
#region Constructors
public ObjectPool() : this(ObjectPool<T>.GetDefaultItemFactory())
{
}
public ObjectPool(Func<T> itemFactory) : this(itemFactory, null, ObjectPoolOptions.NoRestrictions)
{
}
public ObjectPool(Func<T> itemFactory, Action<T> itemCleanup, ObjectPoolOptions options)
{
this.itemFactory = itemFactory;
this.itemCleanup = itemCleanup;
this.minPoolSize = options.MinPoolSize;
this.maxPoolSize = options.MaxPoolSize;
this.maxItemAge = options.MaxItemAge;
this.innerPool = new BlockingCollection<ObjectPoolEntry<T>>(new ConcurrentBag<ObjectPoolEntry<T>>(), this.maxPoolSize);
if(this.minPoolSize > 0)
{
this.EnsureMinPoolSize();
}
}
#endregion
#region Type specific methods
public T Take()
{
ObjectPool<T>.TraceEvent(TraceEventType.Verbose, 0, "Take", "called");
ObjectPoolEntry<T> entry;
T result = null;
while(result == null
&&
this.innerPool.TryTake(out entry))
{
ObjectPool<T>.TraceEvent(TraceEventType.Information, 0, "Take", "found pooled instance");
// Figure out how long the item's been in the cache
TimeSpan itemAge = DateTime.Now - entry.LastUsed;
T item = entry.Item;
// Only use this item if it's not too old
if(itemAge < this.maxItemAge)
{
result = item;
}
else
{
ObjectPool<T>.TraceEvent(TraceEventType.Warning, 0, "Take", "pooled instance age, {0}, is past max item age setting of {1}, cleaning up and looking for another instance...", itemAge, this.maxItemAge);
try
{
this.itemCleanup(item);
}
catch(Exception exception)
{
ObjectPool<T>.TraceEvent(TraceEventType.Error, 3, "Take", "An exception occurred while cleaning up an instance:\r\n", exception);
}
}
}
// If we were unable to find a pooled instance, we will have to try to create a new one now
if(result == null)
{
ObjectPool<T>.TraceEvent(TraceEventType.Information, 0, "Take", "no pooled instance available");
int currentOutstandingLeases = this.outstandingLeases;
int currentInstancesBeingCreated = Interlocked.Increment(ref this.instancesBeingCreated);
if(currentOutstandingLeases + currentInstancesBeingCreated <= this.maxPoolSize)
{
try
{
ObjectPool<T>.TraceEvent(TraceEventType.Information, 0, "Take", "max pool size {0} has not been reached (only {1} outstanding, {2} being created), creating new instance now...", this.maxPoolSize, currentOutstandingLeases, currentInstancesBeingCreated);
result = this.itemFactory();
}
catch(Exception exception)
{
ObjectPool<T>.TraceEvent(TraceEventType.Error, 1, "Take", "exception occurred creating new instance:\r\n" + exception);
throw new Exception("An exception occurred calling the itemFactory method to create a new instance. Please check the inner exception for more details.", exception);
}
finally
{
Interlocked.Decrement(ref this.instancesBeingCreated);
}
}
else
{
Interlocked.Decrement(ref this.instancesBeingCreated);
ObjectPool<T>.TraceEvent(TraceEventType.Warning, 2, "Take", "max pool size has been reached {0}, need to wait for an instance to be returned to the pool before returning to the caller.", this.maxPoolSize);
entry = this.innerPool.Take();
ObjectPool<T>.TraceEvent(TraceEventType.Information, 0, "Take", "an item was returned to the pool, we can now return that to this caller.");
result = entry.Item;
}
}
Interlocked.Increment(ref this.outstandingLeases);
ObjectPool<T>.TraceEvent(TraceEventType.Information, 0, "Take", "now have {0} outstanding lease(s) and {1} pooled resources.", this.outstandingLeases, this.innerPool.Count);
return result;
}
public void Return(T item)
{
ObjectPool<T>.TraceEvent(TraceEventType.Verbose, 0, "Return", "item being returned to the pool...");
this.innerPool.Add(new ObjectPoolEntry<T>(item));
Interlocked.Decrement(ref this.outstandingLeases);
ObjectPool<T>.TraceEvent(TraceEventType.Information, 0, "Return", "item returned to pool, now have {0} outstanding lease(s) and {1} pooled resources.", this.outstandingLeases, this.innerPool.Count);
}
public void Abandon(T item)
{
Interlocked.Decrement(ref this.outstandingLeases);
ObjectPool<T>.TraceEvent(TraceEventType.Warning, 4, "Abandon", "asked told to abandon a leased instance, now have {0} outstanding lease(s) and {1} pooled resources.", this.outstandingLeases, this.innerPool.Count);
}
#endregion
#region Helper methods
private void EnsureMinPoolSize()
{
if(this.minPoolSize > 0)
{
Task.Factory.StartNew(() =>
{
int itemInstancesContributed = 0;
ObjectPool<T>.TraceEvent(TraceEventType.Information, 0, "EnsureMinPoolSize", "starting population of pool...");
int currentInstancesBeingCreated = this.instancesBeingCreated;
while((this.innerPool.Count + currentInstancesBeingCreated + this.outstandingLeases) < this.minPoolSize)
{
currentInstancesBeingCreated = Interlocked.Increment(ref this.instancesBeingCreated);
try
{
this.innerPool.Add(new ObjectPoolEntry<T>(this.itemFactory()));
itemInstancesContributed++;
}
finally
{
currentInstancesBeingCreated = Interlocked.Decrement(ref this.instancesBeingCreated);
}
}
ObjectPool<T>.TraceEvent(TraceEventType.Information, 0, "EnsureMinPoolSize", "finished population of pool, {0} instances contributed.", itemInstancesContributed);
});
}
}
private static Func<T> GetDefaultItemFactory()
{
return () => new T();
}
private static void TraceEvent(TraceEventType eventType, int eventId, string methodName, string message)
{
if(ObjectPool<T>.TraceSwitch.ShouldTrace(eventType))
{
ObjectPool<T>.TraceSource.TraceEvent(eventType, eventId, "ObjectPool<" + typeof(T).FullName + ">::" + methodName + " - " + message);
}
}
private static void TraceEvent(TraceEventType eventType, int eventId, string methodName, string message, params object[] formatValues)
{
ObjectPool<T>.TraceEvent(eventType, eventId, methodName, string.Format(message, formatValues));
}
#endregion
#region Nested types
private struct ObjectPoolEntry<TItem>
{
#region Fields
public TItem Item;
public DateTime LastUsed;
#endregion
#region Constructors
public ObjectPoolEntry(TItem item)
{
this.Item = item;
this.LastUsed = DateTime.Now;
}
#endregion
}
#endregion
}
public sealed class ObjectPoolOptions
{
#region Fields
public static readonly ObjectPoolOptions NoRestrictions = new ObjectPoolOptions
{
MinPoolSize = 0,
MaxPoolSize = int.MaxValue,
MaxItemAge = TimeSpan.Zero
};
#endregion
#region Type specific properties
public int MinPoolSize
{
get;
set;
}
public int MaxPoolSize
{
get;
set;
}
public TimeSpan MaxItemAge
{
get;
set;
}
#endregion
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment