using System; using System.Diagnostics; using System.Threading; namespace ZeroLevel.Services.Pools { /// /// Steal from Roslyn /// https://github.com/dotnet/roslyn/blob/master/src/Dependencies/PooledObjects/ObjectPool%601.cs /// public class ObjectPool where T : class { [DebuggerDisplay("{Value,nq}")] private struct Element { internal T Value; } /// /// Not using System.Func{T} because this file is linked into the (debugger) Formatter, /// which does not have that type (since it compiles against .NET 2.0). /// public delegate T Factory(); // Storage for the pool objects. The first item is stored in a dedicated field because we // expect to be able to satisfy most requests from it. private T _firstItem; private readonly Element[] _items; // factory is stored for the lifetime of the pool. We will call this only when pool needs to // expand. compared to "new T()", Func gives more flexibility to implementers and faster // than "new T()". private readonly Factory _factory; public ObjectPool(Factory factory) : this(factory, Environment.ProcessorCount * 2) { } public ObjectPool(Factory factory, int size) { Debug.Assert(size >= 1); _factory = factory; _items = new Element[size - 1]; } private T CreateInstance() { var inst = _factory(); return inst; } /// /// Produces an instance. /// /// /// Search strategy is a simple linear probing which is chosen for it cache-friendliness. /// Note that Free will try to store recycled objects close to the start thus statistically /// reducing how far we will typically search. /// public T Allocate() { // PERF: Examine the first element. If that fails, AllocateSlow will look at the remaining elements. // Note that the initial read is optimistically not synchronized. That is intentional. // We will interlock only when we have a candidate. in a worst case we may miss some // recently returned objects. Not a big deal. T inst = _firstItem; if (inst == null || inst != Interlocked.CompareExchange(ref _firstItem, null, inst)) { inst = AllocateSlow(); } return inst; } private T AllocateSlow() { var items = _items; for (int i = 0; i < items.Length; i++) { // Note that the initial read is optimistically not synchronized. That is intentional. // We will interlock only when we have a candidate. in a worst case we may miss some // recently returned objects. Not a big deal. T inst = items[i].Value; if (inst != null) { if (inst == Interlocked.CompareExchange(ref items[i].Value, null, inst)) { return inst; } } } return CreateInstance(); } /// /// Returns objects to the pool. /// /// /// Search strategy is a simple linear probing which is chosen for it cache-friendliness. /// Note that Free will try to store recycled objects close to the start thus statistically /// reducing how far we will typically search in Allocate. /// public void Free(T obj) { if (!Validate(obj)) { return; } if (_firstItem == null) { // Intentionally not using interlocked here. // In a worst case scenario two objects may be stored into same slot. // It is very unlikely to happen and will only mean that one of the objects will get collected. _firstItem = obj; } else { FreeSlow(obj); } } private void FreeSlow(T obj) { var items = _items; for (int i = 0; i < items.Length; i++) { if (items[i].Value == null) { // Intentionally not using interlocked here. // In a worst case scenario two objects may be stored into same slot. // It is very unlikely to happen and will only mean that one of the objects will get collected. items[i].Value = obj; break; } } } private bool Validate(object obj) { if (obj == null) return false; if (_firstItem == obj) return false; var items = _items; for (int i = 0; i < items.Length; i++) { var value = items[i].Value; if (value == null) { return true; } if (value == obj) return false; } return true; } } /* Alternate https://stackoverflow.com/questions/1698738/objectpoolt-or-similar-for-net-already-in-a-library */ }