corrade-vassal – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) 2006-2014, openmetaverse.org
3 * All rights reserved.
4 *
5 * - Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 *
8 * - Redistributions of source code must retain the above copyright notice, this
9 * list of conditions and the following disclaimer.
10 * - Neither the name of the openmetaverse.org nor the names
11 * of its contributors may be used to endorse or promote products derived from
12 * this software without specific prior written permission.
13 *
14 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
15 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
17 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
18 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
19 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
20 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
21 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
22 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
23 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
24 * POSSIBILITY OF SUCH DAMAGE.
25 */
26  
27 using System;
28 using System.Threading;
29 using System.Collections.Generic;
30  
31 namespace OpenMetaverse
32 {
33 #region TimedCacheKey Class
34  
35 class TimedCacheKey<TKey> : IComparable<TKey>
36 {
37 private DateTime expirationDate;
38 private bool slidingExpiration;
39 private TimeSpan slidingExpirationWindowSize;
40 private TKey key;
41  
42 public DateTime ExpirationDate { get { return expirationDate; } }
43 public TKey Key { get { return key; } }
44 public bool SlidingExpiration { get { return slidingExpiration; } }
45 public TimeSpan SlidingExpirationWindowSize { get { return slidingExpirationWindowSize; } }
46  
47 public TimedCacheKey(TKey key, DateTime expirationDate)
48 {
49 this.key = key;
50 this.slidingExpiration = false;
51 this.expirationDate = expirationDate;
52 }
53  
54 public TimedCacheKey(TKey key, TimeSpan slidingExpirationWindowSize)
55 {
56 this.key = key;
57 this.slidingExpiration = true;
58 this.slidingExpirationWindowSize = slidingExpirationWindowSize;
59 Accessed();
60 }
61  
62 public void Accessed()
63 {
64 if (slidingExpiration)
65 expirationDate = DateTime.Now.Add(slidingExpirationWindowSize);
66 }
67  
68 public int CompareTo(TKey other)
69 {
70 return key.GetHashCode().CompareTo(other.GetHashCode());
71 }
72 }
73  
74 #endregion
75  
76 public sealed class ExpiringCache<TKey, TValue>
77 {
78 const double CACHE_PURGE_HZ = 1.0;
79 const int MAX_LOCK_WAIT = 5000; // milliseconds
80  
81 #region Private fields
82  
83 /// <summary>For thread safety</summary>
84 object syncRoot = new object();
85 /// <summary>For thread safety</summary>
86 object isPurging = new object();
87  
88 Dictionary<TimedCacheKey<TKey>, TValue> timedStorage = new Dictionary<TimedCacheKey<TKey>, TValue>();
89 Dictionary<TKey, TimedCacheKey<TKey>> timedStorageIndex = new Dictionary<TKey, TimedCacheKey<TKey>>();
90 private System.Timers.Timer timer = new System.Timers.Timer(TimeSpan.FromSeconds(CACHE_PURGE_HZ).TotalMilliseconds);
91  
92 #endregion
93  
94 #region Constructor
95  
96 public ExpiringCache()
97 {
98 timer.Elapsed += PurgeCache;
99 timer.Start();
100 }
101  
102 #endregion
103  
104 #region Public methods
105  
106 public bool Add(TKey key, TValue value, double expirationSeconds)
107 {
108 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
109 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
110 try
111 {
112 // This is the actual adding of the key
113 if (timedStorageIndex.ContainsKey(key))
114 {
115 return false;
116 }
117 else
118 {
119 TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, DateTime.UtcNow + TimeSpan.FromSeconds(expirationSeconds));
120 timedStorage.Add(internalKey, value);
121 timedStorageIndex.Add(key, internalKey);
122 return true;
123 }
124 }
125 finally { Monitor.Exit(syncRoot); }
126 }
127  
128 public bool Add(TKey key, TValue value, TimeSpan slidingExpiration)
129 {
130 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
131 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
132 try
133 {
134 // This is the actual adding of the key
135 if (timedStorageIndex.ContainsKey(key))
136 {
137 return false;
138 }
139 else
140 {
141 TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, slidingExpiration);
142 timedStorage.Add(internalKey, value);
143 timedStorageIndex.Add(key, internalKey);
144 return true;
145 }
146 }
147 finally { Monitor.Exit(syncRoot); }
148 }
149  
150 public bool AddOrUpdate(TKey key, TValue value, double expirationSeconds)
151 {
152 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
153 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
154 try
155 {
156 if (Contains(key))
157 {
158 Update(key, value, expirationSeconds);
159 return false;
160 }
161 else
162 {
163 Add(key, value, expirationSeconds);
164 return true;
165 }
166 }
167 finally { Monitor.Exit(syncRoot); }
168 }
169  
170 public bool AddOrUpdate(TKey key, TValue value, TimeSpan slidingExpiration)
171 {
172 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
173 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
174 try
175 {
176 if (Contains(key))
177 {
178 Update(key, value, slidingExpiration);
179 return false;
180 }
181 else
182 {
183 Add(key, value, slidingExpiration);
184 return true;
185 }
186 }
187 finally { Monitor.Exit(syncRoot); }
188 }
189  
190 public void Clear()
191 {
192 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
193 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
194 try
195 {
196 timedStorage.Clear();
197 timedStorageIndex.Clear();
198 }
199 finally { Monitor.Exit(syncRoot); }
200 }
201  
202 public bool Contains(TKey key)
203 {
204 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
205 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
206 try
207 {
208 return timedStorageIndex.ContainsKey(key);
209 }
210 finally { Monitor.Exit(syncRoot); }
211 }
212  
213 public int Count
214 {
215 get
216 {
217 return timedStorage.Count;
218 }
219 }
220  
221 public object this[TKey key]
222 {
223 get
224 {
225 TValue o;
226 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
227 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
228 try
229 {
230 if (timedStorageIndex.ContainsKey(key))
231 {
232 TimedCacheKey<TKey> tkey = timedStorageIndex[key];
233 o = timedStorage[tkey];
234 timedStorage.Remove(tkey);
235 tkey.Accessed();
236 timedStorage.Add(tkey, o);
237 return o;
238 }
239 else
240 {
241 throw new ArgumentException("Key not found in the cache");
242 }
243 }
244 finally { Monitor.Exit(syncRoot); }
245 }
246 }
247  
248 public bool Remove(TKey key)
249 {
250 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
251 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
252 try
253 {
254 if (timedStorageIndex.ContainsKey(key))
255 {
256 timedStorage.Remove(timedStorageIndex[key]);
257 timedStorageIndex.Remove(key);
258 return true;
259 }
260 else
261 {
262 return false;
263 }
264 }
265 finally { Monitor.Exit(syncRoot); }
266 }
267  
268 public bool TryGetValue(TKey key, out TValue value)
269 {
270 TValue o;
271  
272 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
273 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
274 try
275 {
276 if (timedStorageIndex.ContainsKey(key))
277 {
278 TimedCacheKey<TKey> tkey = timedStorageIndex[key];
279 o = timedStorage[tkey];
280 timedStorage.Remove(tkey);
281 tkey.Accessed();
282 timedStorage.Add(tkey, o);
283 value = o;
284 return true;
285 }
286 }
287 finally { Monitor.Exit(syncRoot); }
288  
289 value = default(TValue);
290 return false;
291 }
292  
293 public bool Update(TKey key, TValue value)
294 {
295 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
296 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
297 try
298 {
299 if (timedStorageIndex.ContainsKey(key))
300 {
301 timedStorage.Remove(timedStorageIndex[key]);
302 timedStorageIndex[key].Accessed();
303 timedStorage.Add(timedStorageIndex[key], value);
304 return true;
305 }
306 else
307 {
308 return false;
309 }
310 }
311 finally { Monitor.Exit(syncRoot); }
312 }
313  
314 public bool Update(TKey key, TValue value, double expirationSeconds)
315 {
316 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
317 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
318 try
319 {
320 if (timedStorageIndex.ContainsKey(key))
321 {
322 timedStorage.Remove(timedStorageIndex[key]);
323 timedStorageIndex.Remove(key);
324 }
325 else
326 {
327 return false;
328 }
329  
330 TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, DateTime.UtcNow + TimeSpan.FromSeconds(expirationSeconds));
331 timedStorage.Add(internalKey, value);
332 timedStorageIndex.Add(key, internalKey);
333 return true;
334 }
335 finally { Monitor.Exit(syncRoot); }
336 }
337  
338 public bool Update(TKey key, TValue value, TimeSpan slidingExpiration)
339 {
340 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
341 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
342 try
343 {
344 if (timedStorageIndex.ContainsKey(key))
345 {
346 timedStorage.Remove(timedStorageIndex[key]);
347 timedStorageIndex.Remove(key);
348 }
349 else
350 {
351 return false;
352 }
353  
354 TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, slidingExpiration);
355 timedStorage.Add(internalKey, value);
356 timedStorageIndex.Add(key, internalKey);
357 return true;
358 }
359 finally { Monitor.Exit(syncRoot); }
360 }
361  
362 public void CopyTo(Array array, int startIndex)
363 {
364 // Error checking
365 if (array == null) { throw new ArgumentNullException("array"); }
366  
367 if (startIndex < 0) { throw new ArgumentOutOfRangeException("startIndex", "startIndex must be >= 0."); }
368  
369 if (array.Rank > 1) { throw new ArgumentException("array must be of Rank 1 (one-dimensional)", "array"); }
370 if (startIndex >= array.Length) { throw new ArgumentException("startIndex must be less than the length of the array.", "startIndex"); }
371 if (Count > array.Length - startIndex) { throw new ArgumentException("There is not enough space from startIndex to the end of the array to accomodate all items in the cache."); }
372  
373 // Copy the data to the array (in a thread-safe manner)
374 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
375 throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
376 try
377 {
378 foreach (object o in timedStorage)
379 {
380 array.SetValue(o, startIndex);
381 startIndex++;
382 }
383 }
384 finally { Monitor.Exit(syncRoot); }
385 }
386  
387 #endregion
388  
389 #region Private methods
390  
391 /// <summary>
392 /// Purges expired objects from the cache. Called automatically by the purge timer.
393 /// </summary>
394 private void PurgeCache(object sender, System.Timers.ElapsedEventArgs e)
395 {
396 // Only let one thread purge at once - a buildup could cause a crash
397 // This could cause the purge to be delayed while there are lots of read/write ops
398 // happening on the cache
399 if (!Monitor.TryEnter(isPurging))
400 return;
401  
402 DateTime signalTime = DateTime.UtcNow;
403  
404 try
405 {
406 // If we fail to acquire a lock on the synchronization root after MAX_LOCK_WAIT, skip this purge cycle
407 if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
408 return;
409 try
410 {
411 Lazy<List<object>> expiredItems = new Lazy<List<object>>();
412  
413 foreach (TimedCacheKey<TKey> timedKey in timedStorage.Keys)
414 {
415 if (timedKey.ExpirationDate < signalTime)
416 {
417 // Mark the object for purge
418 expiredItems.Value.Add(timedKey.Key);
419 }
420 else
421 {
422 break;
423 }
424 }
425  
426 if (expiredItems.IsValueCreated)
427 {
428 foreach (TKey key in expiredItems.Value)
429 {
430 TimedCacheKey<TKey> timedKey = timedStorageIndex[key];
431 timedStorageIndex.Remove(timedKey.Key);
432 timedStorage.Remove(timedKey);
433 }
434 }
435 }
436 finally { Monitor.Exit(syncRoot); }
437 }
438 finally { Monitor.Exit(isPurging); }
439 }
440  
441 #endregion
442 }
443 }