forked from kevinkinnett/BitSharp
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathDisposableCache.cs
170 lines (149 loc) · 6.55 KB
/
DisposableCache.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
using BitSharp.Common.ExtensionMethods;
using NLog;
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using System.Threading;
namespace BitSharp.Common
{
/// <summary>
/// <para>A cache for IDiposable instances.</para>
/// <para>The cache will create new instances as required, and will dispose instances which no longer fit in the cache.</para>
/// <para>The cache has a maximum capacity, but more instances than this may be created. The capacity limits how many cached instances will be kept as they are returned.</para>
/// </summary>
/// <typeparam name="T">The type of object being cached, must be IDiposable.</typeparam>
public class DisposableCache<T> : IDisposable where T : class, IDisposable
{
private static readonly Logger logger = LogManager.GetCurrentClassLogger();
private readonly Func<T> createFunc;
private readonly Action<T> prepareAction;
private readonly ConcurrentBag<T> cache;
private int cacheCount;
private readonly AutoResetEvent itemFreedEvent = new AutoResetEvent(false);
private bool isDisposed;
/// <summary>
/// Initializes a new instance of DisposableCache.
/// </summary>
/// <param name="capacity">The maximum number of instances to cache.</param>
/// <param name="createFunc">A function to create new instances. This must be null if dynamically creating new instances is not allowed.</param>
/// <param name="prepareAction">An action to call on instances before they are returned to the cache. This may be null.</param>
public DisposableCache(int capacity, Func<T> createFunc = null, Action<T> prepareAction = null)
{
this.Capacity = capacity;
this.cache = new ConcurrentBag<T>();
this.createFunc = createFunc;
this.prepareAction = prepareAction;
}
/// <summary>
/// Releases all cached instances.
/// </summary>
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (!this.isDisposed && disposing)
{
this.cache.DisposeList();
this.isDisposed = true;
}
}
/// <summary>
/// The maximum capacity of the cache.
/// </summary>
public int Capacity { get; }
/// <summary>
/// Take an instance from the cache. If allowed, a new instance will be created if no cached instances are available, or an available instace will be waited for.
/// </summary>
/// <exception cref="InvalidOperationException">If no cached instance is available when creating new instances is disallowed.</exception>
/// <returns>A handle to an instance of <typeparamref name="T"/>.</returns>
public DisposeHandle<T> TakeItem()
{
return TakeItem(TimeSpan.MaxValue);
}
/// <summary>
/// <para>Take an instance from the cache, with a timeout if no cached instances are available.</para>
/// </summary>
/// <param name="timeout">The timespan to wait for a cache instance to become available.</param>
/// <exception cref="TimeoutException">If no cached instance became available before the timeout expired.</exception>
/// <returns>A handle to an instance of <typeparamref name="T"/>.</returns>
public DisposeHandle<T> TakeItem(TimeSpan timeout)
{
// track running time
var stopwatch = Stopwatch.StartNew();
while (true)
{
// try to take a cache instance
DisposeHandle<T> handle;
if (this.TryTakeItem(out handle))
return handle;
Throttler.IfElapsed(TimeSpan.FromSeconds(5), () =>
logger.Warn($"Disposable cache ran out of items: {GetType().Name}"));
if (timeout < TimeSpan.Zero || timeout == TimeSpan.MaxValue)
{
this.itemFreedEvent.WaitOne();
}
else
{
// determine the amount of timeout remaining
var remaining = timeout - stopwatch.Elapsed;
// if timeout is remaining, wait up to that amount of time for a new instance to become available
if (remaining.Ticks > 0)
this.itemFreedEvent.WaitOne(remaining);
// otherwise, throw a timeout exception
else
throw new TimeoutException();
}
}
}
public bool TryTakeItem(out DisposeHandle<T> handle)
{
T item;
// attempt to take an instance from the cache
if (this.cache.TryTake(out item))
{
Interlocked.Decrement(ref cacheCount);
handle = new DisposeHandle<T>(CacheHandle, item);
return true;
}
// if no instance was available, create a new one if allowed
else if (this.createFunc != null)
{
handle = new DisposeHandle<T>(CacheHandle, this.createFunc());
return true;
}
// no instance was available
else
{
handle = null;
return false;
}
}
/// <summary>
/// Make an instance available to the cache. If the maximum capacity would be exceeded, the instance will be disposed instead of being cached.
/// </summary>
/// <param name="handle">The instance to be cached.</param>
public void CacheItem(T item)
{
CacheHandle(new DisposeHandle<T>(CacheHandle, item));
}
private void CacheHandle(DisposeHandle<T> handle)
{
// prepare the instance to be cached
this.prepareAction?.Invoke(handle.Item);
// attempt to return the instance to the cache
if (Interlocked.Increment(ref cacheCount) <= Capacity)
{
this.cache.Add(handle.Item);
this.itemFreedEvent.Set();
}
else
{
Interlocked.Decrement(ref cacheCount);
handle.Item.Dispose();
}
}
}
}