Skip to content

Commit 7e9d570

Browse files
author
Oren (electricessence)
committed
Added recyclability to all pools.
Recycling only happens if an item can be recieved.
1 parent 5562fe1 commit 7e9d570

12 files changed

Lines changed: 203 additions & 78 deletions

source/BufferBlockObjectPool.cs

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -19,17 +19,27 @@ public class BufferBlockObjectPool<T> : TrimmableObjectPoolBase<T>
1919
/// Constructs an ObjectPool that when .Take() is called will return the first possible item even if one is returned to the pool before the generator function completes.
2020
/// </summary>
2121
/// <param name="factory">The generator function that creates the items.</param>
22+
/// <param name="recycler">The optional function that operates on an item just before entering the pool.</param>
2223
/// <param name="maxSize">The maximum size of the object pool. Default is ushort.MaxValue (65535).</param>
2324
public BufferBlockObjectPool(
2425
Func<T> factory,
25-
int capacity = DEFAULT_CAPACITY) : base(factory, capacity)
26+
Action<T> recycler,
27+
int capacity = DEFAULT_CAPACITY)
28+
: base(factory, recycler, capacity)
2629
{
2730
_pool = new BufferBlock<T>(new DataflowBlockOptions()
2831
{
2932
BoundedCapacity = capacity
3033
});
3134
}
3235

36+
public BufferBlockObjectPool(
37+
Func<T> factory,
38+
int capacity = DEFAULT_CAPACITY)
39+
: this(factory, null, capacity)
40+
{
41+
}
42+
3343
protected BufferBlock<T> _pool;
3444

3545
Task<bool> Generate(out Task<T> actual)
@@ -102,27 +112,17 @@ public override sealed T Take()
102112

103113
protected override void OnDispose(bool calledExplicitly)
104114
{
105-
var pool = Nullify(ref _pool);
106-
if (pool != null)
107-
{
108-
pool.Complete(); // No more... You're done...
109-
if(pool.TryReceiveAll(out IList<T> items)) // Empty out...
110-
items.Clear();
111-
}
115+
Nullify(ref _pool)?.Complete();
112116
}
113117

114118
protected override bool GiveInternal(T item)
115119
{
116-
return (item == null ? null : _pool)
117-
?.Post(item)
118-
?? false;
120+
return _pool.Post(item);
119121
}
120122

121123
protected override Task<bool> GiveInternalAsync(T item)
122124
{
123-
return (item == null ? null : _pool)
124-
?.SendAsync(item)
125-
?? Task.FromResult(false);
125+
return _pool.SendAsync(item);
126126
}
127127

128128
}

source/CollectionWrapperObjectPool.cs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,30 @@ public class CollectionWrapperObjectPool<T, TCollection> : TrimmableObjectPoolBa
1010
where T : class
1111
where TCollection : class, ICollection<T>
1212
{
13-
public CollectionWrapperObjectPool(TCollection pool, Func<T> factory, int capacity = DEFAULT_CAPACITY) : base(factory, capacity)
13+
public CollectionWrapperObjectPool(TCollection pool, Func<T> factory, Action<T> recycler, int capacity = DEFAULT_CAPACITY)
14+
: base(factory, recycler, capacity)
1415
{
1516
Pool = pool;
1617
}
1718

19+
public CollectionWrapperObjectPool(TCollection pool, Func<T> factory, int capacity = DEFAULT_CAPACITY)
20+
: this(pool, factory, null, capacity)
21+
{
22+
}
23+
1824
protected TCollection Pool;
1925

2026
public override int Count => Pool?.Count ?? 0;
2127

2228
protected override bool GiveInternal(T item)
2329
{
24-
if (item != null)
30+
lock (Pool)
2531
{
26-
var p = Pool;
27-
if (p != null && p.Count < MaxSize)
28-
{
29-
lock (p) p.Add(item); // It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt.
30-
return true;
31-
}
32+
if (Count >= MaxSize) return false;
33+
Pool.Add(item);
3234
}
3335

34-
return false;
36+
return true;
3537
}
3638

3739
protected override T TryTakeInternal()
@@ -56,7 +58,7 @@ protected override T TryTakeInternal()
5658

5759
protected override void OnDispose(bool calledExplicitly)
5860
{
59-
Nullify(ref Pool)?.Clear();
61+
Pool = null;
6062
}
6163
}
6264

source/ConcurrentBagObjectPool.cs

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,28 +10,26 @@ public sealed class ConcurrentBagObjectPool<T> : TrimmableObjectPoolBase<T>
1010
where T : class
1111

1212
{
13-
public ConcurrentBagObjectPool(Func<T> factory, int capacity = DEFAULT_CAPACITY) : base(factory, capacity)
13+
14+
public ConcurrentBagObjectPool(Func<T> factory, Action<T> recycler, int capacity = DEFAULT_CAPACITY)
15+
: base(factory, recycler, capacity)
1416
{
1517
Pool = new ConcurrentBag<T>();
1618
}
1719

20+
public ConcurrentBagObjectPool(Func<T> factory, int capacity = DEFAULT_CAPACITY)
21+
: this(factory, null, capacity)
22+
{
23+
}
24+
1825
ConcurrentBag<T> Pool;
1926

2027
public override int Count => Pool?.Count ?? 0;
2128

2229
protected override bool GiveInternal(T item)
2330
{
24-
if (item != null)
25-
{
26-
var p = Pool;
27-
if (p != null && p.Count < MaxSize)
28-
{
29-
p.Add(item); // It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt.
30-
return true;
31-
}
32-
}
33-
34-
return false;
31+
Pool.Add(item); // It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt.
32+
return true;
3533
}
3634

3735
protected override T TryTakeInternal()

source/IObjectPool.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ public interface IObjectPool<T> : IDisposable
2323
/// WARNING: The item is considered 'dead' but resurrectable so be sure not to hold on to the item's reference.
2424
/// </summary>
2525
/// <param name="item">The item to give up to the pool.</param>
26+
/// <param name="recycler">An optional action exectue on the item only if it's possible to return to the pool.</param>
2627
void Give(T item);
2728

2829
/// <summary>
2930
/// Asynchronously receives an item and adds it to the pool. Ignores null references.
3031
/// WARNING: The item is considered 'dead' but resurrectable so be sure not to hold on to the item's reference.
3132
/// </summary>
3233
/// <param name="item">The item to give up to the pool.</param>
34+
/// <param name="recycler">An optional action exectue on the item only if it's possible to return to the pool.</param>
3335
Task GiveAsync(T item);
3436

3537
/// <summary>

source/LinkedListObjectPool.cs

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,17 @@ namespace Open.Disposable
77
public class LinkedListObjectPool<T> : CollectionWrapperObjectPool<T, LinkedList<T>>
88
where T : class
99
{
10-
public LinkedListObjectPool(Func<T> factory, int capacity = DEFAULT_CAPACITY) : base(new LinkedList<T>(), factory, capacity)
10+
public LinkedListObjectPool(Func<T> factory, Action<T> recycler, int capacity = DEFAULT_CAPACITY)
11+
: base(new LinkedList<T>(), factory, recycler, capacity)
1112
{
1213
}
1314

15+
public LinkedListObjectPool(Func<T> factory, int capacity = DEFAULT_CAPACITY)
16+
: this(factory, null, capacity)
17+
{
18+
}
19+
20+
1421
protected override T TryTakeInternal()
1522
{
1623
var p = Pool;

source/ObjectPoolAutoTrimmer.cs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -28,25 +28,25 @@ public class ObjectPoolAutoTrimmer : DisposableBase
2828
/// <summary>
2929
/// Constructs an auto-trimming ObjectPool helper.
3030
/// </summary>
31-
/// <param name="target">The governable object pool to maintain.</param>
31+
/// <param name="pool">The governable object pool to maintain.</param>
3232
/// <param name="trimmedSize">The target size to limit to after a half second timeout. Allowing the pool to still grow to the max size until the trim occurs.</param>
3333
/// <param name="trimDelay">The amount of time to wait/defer trimming.</param>
3434
public ObjectPoolAutoTrimmer(
3535
ushort trimmedSize,
36-
ITrimmableObjectPool target,
36+
ITrimmableObjectPool pool,
3737
TimeSpan? trimDelay = null)
3838
{
39-
_pool = target;
40-
if (target is DisposableBase pool)
41-
pool.BeforeDispose += Pool_BeforeDispose;
39+
_pool = pool ?? throw new ArgumentNullException("pool");
40+
if (pool is DisposableBase d)
41+
d.BeforeDispose += Pool_BeforeDispose;
4242

4343
TrimmedSize = _trimmedSize = trimmedSize;
4444
TrimDelay = _trimDelay = trimDelay ?? TimeSpan.FromMilliseconds(500);
4545

4646
_trimmer = new ActionRunner(TrimInternal);
4747

48-
target.GivenTo += Target_GivenTo;
49-
target.TakenFrom += Target_TakenFrom;
48+
pool.GivenTo += Target_GivenTo;
49+
pool.TakenFrom += Target_TakenFrom;
5050

5151
}
5252

source/ObjectPoolBase.cs

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,22 @@ public abstract class ObjectPoolBase<T> : DisposableBase, IObjectPool<T>
1010
{
1111
protected const int DEFAULT_CAPACITY = Constants.DEFAULT_CAPACITY;
1212

13-
protected ObjectPoolBase(Func<T> factory, int capacity = DEFAULT_CAPACITY)
13+
protected ObjectPoolBase(Func<T> factory, Action<T> recycler, int capacity = DEFAULT_CAPACITY)
1414
{
1515
if (capacity < 1)
1616
throw new ArgumentOutOfRangeException("capacity", capacity, "Must be at least 1.");
1717
Factory = factory ?? throw new ArgumentNullException("factory");
1818
MaxSize = capacity;
19+
Recycler = recycler;
1920
}
2021

22+
protected ObjectPoolBase(Func<T> factory, int capacity = DEFAULT_CAPACITY)
23+
: this(factory, null, capacity)
24+
{
25+
26+
}
2127

28+
protected Action<T> Recycler;
2229
protected int MaxSize;
2330
public int Capacity => MaxSize;
2431

@@ -31,11 +38,14 @@ public T Generate()
3138
return Factory();
3239
}
3340

41+
protected abstract bool CanGive(T item);
42+
43+
// Contract should be that no item can be null here.
3444
protected abstract bool GiveInternal(T item);
3545

3646
public virtual void Give(T item)
3747
{
38-
GiveInternal(item);
48+
if(CanGive(item)) GiveInternal(item);
3949
}
4050

4151
protected virtual Task<bool> GiveInternalAsync(T item)
@@ -45,6 +55,7 @@ protected virtual Task<bool> GiveInternalAsync(T item)
4555

4656
public virtual Task GiveAsync(T item)
4757
{
58+
if (item == null) return Task.FromResult(false);
4859
return GiveInternalAsync(item);
4960
}
5061

source/OptimisticArrayObjectPool.cs

Lines changed: 23 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,20 @@ namespace Open.Disposable
1313
public class OptimisticArrayObjectPool<T> : ObjectPoolBase<T>
1414
where T : class
1515
{
16-
public OptimisticArrayObjectPool(Func<T> factory, int capacity = DEFAULT_CAPACITY) : base(factory, capacity)
16+
17+
public OptimisticArrayObjectPool(Func<T> factory, Action<T> recycler, int capacity = DEFAULT_CAPACITY)
18+
: base(factory, recycler, capacity)
1719
{
1820
_pool = new Element[capacity - 1];
1921
}
2022

23+
public OptimisticArrayObjectPool(Func<T> factory, int capacity = DEFAULT_CAPACITY)
24+
: this(factory, null, capacity)
25+
{
26+
}
27+
28+
29+
2130
[DebuggerDisplay("{Value,nq}")]
2231
private struct Element
2332
{
@@ -27,10 +36,18 @@ private struct Element
2736
Element[] _pool;
2837
T _firstItem;
2938

30-
protected override bool GiveInternal(T item)
39+
protected override bool CanGive(T item)
40+
{
41+
throw new NotImplementedException();
42+
}
43+
44+
public override void Give(T item)
3145
{
32-
if (item == null) return false;
46+
GiveInternal(item);
47+
}
3348

49+
protected override bool GiveInternal(T item)
50+
{
3451
// First see if optimisically we can store in _firstItem;
3552
if (_firstItem == null)
3653
{
@@ -88,13 +105,11 @@ protected override T TryTakeInternal()
88105

89106
protected override void OnDispose(bool calledExplicitly)
90107
{
108+
_pool = null;
91109
_firstItem = null;
92-
var pool = Nullify(ref _pool);
93-
var len = pool?.Length ?? 0;
94-
95-
for (var i = 0; i < len; i++)
96-
pool[i].Value = null;
97110
}
111+
112+
98113
}
99114

100115
public static class OptimisticArrayObjectPool

source/QueueObjectPool.cs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Text;
4+
5+
namespace Open.Disposable
6+
{
7+
public sealed class QueueObjectPool<T> : TrimmableObjectPoolBase<T>
8+
where T : class
9+
{
10+
11+
public QueueObjectPool(Func<T> factory, Action<T> recycler, int capacity = DEFAULT_CAPACITY)
12+
: base(factory, recycler, capacity)
13+
{
14+
Pool = new Queue<T>(capacity); // Very very slight speed improvment when capacity is set.
15+
}
16+
17+
public QueueObjectPool(Func<T> factory, int capacity = DEFAULT_CAPACITY)
18+
: this(factory, null, capacity)
19+
{
20+
21+
}
22+
23+
Queue<T> Pool;
24+
25+
public override int Count => Pool?.Count ?? 0;
26+
27+
protected override bool GiveInternal(T item)
28+
{
29+
if (Count < MaxSize)
30+
{
31+
lock (Pool) Pool.Enqueue(item); // It's possible that the count could exceed MaxSize here, but the risk is negligble as a few over the limit won't hurt.
32+
return true;
33+
}
34+
35+
return false;
36+
}
37+
38+
protected override T TryTakeInternal()
39+
{
40+
var p = Pool;
41+
if (p!=null && p.Count != 0)
42+
{
43+
lock (p)
44+
{
45+
if (p.Count!=0)
46+
return p.Dequeue();
47+
}
48+
49+
}
50+
51+
return null;
52+
}
53+
54+
protected override void OnDispose(bool calledExplicitly)
55+
{
56+
Pool = null;
57+
}
58+
}
59+
60+
public static class QueueObjectPool
61+
{
62+
public static QueueObjectPool<T> Create<T>(Func<T> factory, int capacity = Constants.DEFAULT_CAPACITY)
63+
where T : class
64+
{
65+
return new QueueObjectPool<T>(factory, capacity);
66+
}
67+
68+
public static QueueObjectPool<T> Create<T>(int capacity = Constants.DEFAULT_CAPACITY)
69+
where T : class, new()
70+
{
71+
return Create(() => new T(), capacity);
72+
}
73+
}
74+
}

0 commit comments

Comments
 (0)