PageRenderTime 56ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/trunk/managed/libomv/LibOMV_2568/OpenMetaverse/Types/ExpiringCache.cs

https://bitbucket.org/KyanhaLLC/opensim-libs
C# | 558 lines | 444 code | 54 blank | 60 comment | 29 complexity | 16e9669384195c8edf86232bdd6939b1 MD5 | raw file
Possible License(s): Apache-2.0, BSD-2-Clause, MIT, LGPL-2.1, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, GPL-3.0, BSD-3-Clause
  1. /*
  2. * Copyright (c) 2008, 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. using System;
  27. using System.Threading;
  28. using System.Collections.Generic;
  29. namespace OpenMetaverse
  30. {
  31. #region TimedCacheKey Class
  32. class TimedCacheKey<TKey> : IComparable<TKey>
  33. {
  34. private DateTime expirationDate;
  35. private bool slidingExpiration;
  36. private TimeSpan slidingExpirationWindowSize;
  37. private TKey key;
  38. public DateTime ExpirationDate { get { return expirationDate; } }
  39. public TKey Key { get { return key; } }
  40. public bool SlidingExpiration { get { return slidingExpiration; } }
  41. public TimeSpan SlidingExpirationWindowSize { get { return slidingExpirationWindowSize; } }
  42. public TimedCacheKey(TKey key, DateTime expirationDate)
  43. {
  44. this.key = key;
  45. this.slidingExpiration = false;
  46. this.expirationDate = expirationDate;
  47. }
  48. public TimedCacheKey(TKey key, TimeSpan slidingExpirationWindowSize)
  49. {
  50. this.key = key;
  51. this.slidingExpiration = true;
  52. this.slidingExpirationWindowSize = slidingExpirationWindowSize;
  53. Accessed();
  54. }
  55. public void Accessed()
  56. {
  57. if (slidingExpiration)
  58. expirationDate = DateTime.Now.Add(slidingExpirationWindowSize);
  59. }
  60. public int CompareTo(TKey other)
  61. {
  62. return key.GetHashCode().CompareTo(other.GetHashCode());
  63. }
  64. }
  65. #endregion
  66. public sealed class ExpiringCache<TKey, TValue>
  67. {
  68. #region Private fields
  69. /// <summary>For thread safety</summary>
  70. ReaderWriterLock readWriteLock = new ReaderWriterLock();
  71. const double CACHE_PURGE_HZ = 1.0;
  72. const int MAX_LOCK_WAIT = 5000; // milliseconds
  73. Dictionary<TimedCacheKey<TKey>, TValue> timedStorage = new Dictionary<TimedCacheKey<TKey>, TValue>();
  74. Dictionary<TKey, TimedCacheKey<TKey>> timedStorageIndex = new Dictionary<TKey, TimedCacheKey<TKey>>();
  75. private System.Timers.Timer timer = new System.Timers.Timer(TimeSpan.FromSeconds(CACHE_PURGE_HZ).TotalMilliseconds);
  76. object isPurging = new object();
  77. #endregion
  78. #region Constructor
  79. public ExpiringCache()
  80. {
  81. timer.Elapsed += PurgeCache;
  82. timer.Start();
  83. }
  84. #endregion
  85. #region Public methods
  86. public bool Add(TKey key, TValue value, DateTime expiration)
  87. {
  88. // Synchronise access to storage structures. A read lock may
  89. // already be acquired before this method is called.
  90. bool LockUpgraded = readWriteLock.IsReaderLockHeld;
  91. LockCookie lc = new LockCookie();
  92. if (LockUpgraded)
  93. {
  94. lc = readWriteLock.UpgradeToWriterLock(MAX_LOCK_WAIT);
  95. }
  96. else
  97. {
  98. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  99. }
  100. try
  101. {
  102. // This is the actual adding of the key
  103. if (timedStorageIndex.ContainsKey(key))
  104. {
  105. return false;
  106. }
  107. else
  108. {
  109. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, expiration);
  110. timedStorage.Add(internalKey, value);
  111. timedStorageIndex.Add(key, internalKey);
  112. return true;
  113. }
  114. }
  115. finally
  116. {
  117. // Restore lock state
  118. if (LockUpgraded)
  119. {
  120. readWriteLock.DowngradeFromWriterLock(ref lc);
  121. }
  122. else
  123. {
  124. readWriteLock.ReleaseWriterLock();
  125. }
  126. }
  127. }
  128. public bool Add(TKey key, TValue value, TimeSpan slidingExpiration)
  129. {
  130. // Synchronise access to storage structures. A read lock may
  131. // already be acquired before this method is called.
  132. bool LockUpgraded = readWriteLock.IsReaderLockHeld;
  133. LockCookie lc = new LockCookie();
  134. if (LockUpgraded)
  135. {
  136. lc = readWriteLock.UpgradeToWriterLock(MAX_LOCK_WAIT);
  137. }
  138. else
  139. {
  140. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  141. }
  142. try
  143. {
  144. // This is the actual adding of the key
  145. if (timedStorageIndex.ContainsKey(key))
  146. {
  147. return false;
  148. }
  149. else
  150. {
  151. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, slidingExpiration);
  152. timedStorage.Add(internalKey, value);
  153. timedStorageIndex.Add(key, internalKey);
  154. return true;
  155. }
  156. }
  157. finally
  158. {
  159. // Restore lock state
  160. if (LockUpgraded)
  161. {
  162. readWriteLock.DowngradeFromWriterLock(ref lc);
  163. }
  164. else
  165. {
  166. readWriteLock.ReleaseWriterLock();
  167. }
  168. }
  169. }
  170. public bool AddOrUpdate(TKey key, TValue value, DateTime expiration)
  171. {
  172. readWriteLock.AcquireReaderLock(MAX_LOCK_WAIT);
  173. try
  174. {
  175. if (Contains(key))
  176. {
  177. Update(key, value, expiration);
  178. return false;
  179. }
  180. else
  181. {
  182. Add(key, value, expiration);
  183. return true;
  184. }
  185. }
  186. finally { readWriteLock.ReleaseReaderLock(); }
  187. }
  188. public bool AddOrUpdate(TKey key, TValue value, TimeSpan slidingExpiration)
  189. {
  190. readWriteLock.AcquireReaderLock(MAX_LOCK_WAIT);
  191. try
  192. {
  193. if (Contains(key))
  194. {
  195. Update(key, value, slidingExpiration);
  196. return false;
  197. }
  198. else
  199. {
  200. Add(key, value, slidingExpiration);
  201. return true;
  202. }
  203. }
  204. finally { readWriteLock.ReleaseReaderLock(); }
  205. }
  206. public void Clear()
  207. {
  208. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  209. try
  210. {
  211. timedStorage.Clear();
  212. timedStorageIndex.Clear();
  213. }
  214. finally { readWriteLock.ReleaseWriterLock(); }
  215. }
  216. public bool Contains(TKey key)
  217. {
  218. readWriteLock.AcquireReaderLock(MAX_LOCK_WAIT);
  219. try
  220. {
  221. return timedStorageIndex.ContainsKey(key);
  222. }
  223. finally { readWriteLock.ReleaseReaderLock(); }
  224. }
  225. public int Count
  226. {
  227. get
  228. {
  229. return timedStorage.Count;
  230. }
  231. }
  232. public object this[TKey key]
  233. {
  234. get
  235. {
  236. TValue o;
  237. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  238. try
  239. {
  240. if (timedStorageIndex.ContainsKey(key))
  241. {
  242. TimedCacheKey<TKey> tkey = timedStorageIndex[key];
  243. o = timedStorage[tkey];
  244. timedStorage.Remove(tkey);
  245. tkey.Accessed();
  246. timedStorage.Add(tkey, o);
  247. return o;
  248. }
  249. else
  250. {
  251. throw new ArgumentException("Key not found in the cache");
  252. }
  253. }
  254. finally { readWriteLock.ReleaseWriterLock(); }
  255. }
  256. }
  257. public TValue this[TKey key, DateTime expiration]
  258. {
  259. set
  260. {
  261. AddOrUpdate(key, value, expiration);
  262. }
  263. }
  264. public TValue this[TKey key, TimeSpan slidingExpiration]
  265. {
  266. set
  267. {
  268. AddOrUpdate(key, value, slidingExpiration);
  269. }
  270. }
  271. public bool Remove(TKey key)
  272. {
  273. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  274. try
  275. {
  276. if (timedStorageIndex.ContainsKey(key))
  277. {
  278. timedStorage.Remove(timedStorageIndex[key]);
  279. timedStorageIndex.Remove(key);
  280. return true;
  281. }
  282. else
  283. {
  284. return false;
  285. }
  286. }
  287. finally { readWriteLock.ReleaseWriterLock(); }
  288. }
  289. public bool TryGetValue(TKey key, out TValue value)
  290. {
  291. TValue o;
  292. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  293. try
  294. {
  295. if (timedStorageIndex.ContainsKey(key))
  296. {
  297. TimedCacheKey<TKey> tkey = timedStorageIndex[key];
  298. o = timedStorage[tkey];
  299. timedStorage.Remove(tkey);
  300. tkey.Accessed();
  301. timedStorage.Add(tkey, o);
  302. value = o;
  303. return true;
  304. }
  305. else
  306. {
  307. value = default(TValue);
  308. return false;
  309. }
  310. }
  311. finally { readWriteLock.ReleaseReaderLock(); }
  312. }
  313. /// <summary>
  314. /// Enumerates over all of the stored values without updating access times
  315. /// </summary>
  316. /// <param name="action">Action to perform on all of the elements</param>
  317. public void ForEach(Action<TValue> action)
  318. {
  319. readWriteLock.AcquireReaderLock(MAX_LOCK_WAIT);
  320. try
  321. {
  322. foreach (TValue value in timedStorage.Values)
  323. action(value);
  324. }
  325. finally { readWriteLock.ReleaseReaderLock(); }
  326. }
  327. public bool Update(TKey key, TValue value)
  328. {
  329. // Synchronise access to storage structures. A read lock may
  330. // already be acquired before this method is called.
  331. LockCookie lc = new LockCookie();
  332. bool lockUpgrade = readWriteLock.IsReaderLockHeld;
  333. if (lockUpgrade)
  334. lc = readWriteLock.UpgradeToWriterLock(MAX_LOCK_WAIT);
  335. else
  336. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  337. try
  338. {
  339. if (timedStorageIndex.ContainsKey(key))
  340. {
  341. timedStorage.Remove(timedStorageIndex[key]);
  342. timedStorageIndex[key].Accessed();
  343. timedStorage.Add(timedStorageIndex[key], value);
  344. return true;
  345. }
  346. else
  347. {
  348. return false;
  349. }
  350. }
  351. finally
  352. {
  353. // Restore lock state
  354. if (lockUpgrade)
  355. readWriteLock.DowngradeFromWriterLock(ref lc);
  356. else
  357. readWriteLock.ReleaseWriterLock();
  358. }
  359. }
  360. public bool Update(TKey key, TValue value, DateTime expiration)
  361. {
  362. // Synchronise access to storage structures. A read lock may
  363. // already be acquired before this method is called.
  364. LockCookie lc = new LockCookie();
  365. bool lockUpgrade = readWriteLock.IsReaderLockHeld;
  366. if (lockUpgrade)
  367. lc = readWriteLock.UpgradeToWriterLock(MAX_LOCK_WAIT);
  368. else
  369. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  370. try
  371. {
  372. if (timedStorageIndex.ContainsKey(key))
  373. {
  374. timedStorage.Remove(timedStorageIndex[key]);
  375. timedStorageIndex.Remove(key);
  376. }
  377. else
  378. {
  379. return false;
  380. }
  381. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, expiration);
  382. timedStorage.Add(internalKey, value);
  383. timedStorageIndex.Add(key, internalKey);
  384. return true;
  385. }
  386. finally
  387. {
  388. // Restore lock state
  389. if (lockUpgrade)
  390. readWriteLock.DowngradeFromWriterLock(ref lc);
  391. else
  392. readWriteLock.ReleaseWriterLock();
  393. }
  394. }
  395. public bool Update(TKey key, TValue value, TimeSpan slidingExpiration)
  396. {
  397. // Synchronise access to storage structures. A read lock may
  398. // already be acquired before this method is called.
  399. LockCookie lc = new LockCookie();
  400. bool lockUpgrade = readWriteLock.IsReaderLockHeld;
  401. if (lockUpgrade)
  402. lc = readWriteLock.UpgradeToWriterLock(MAX_LOCK_WAIT);
  403. else
  404. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  405. try
  406. {
  407. if (timedStorageIndex.ContainsKey(key))
  408. {
  409. timedStorage.Remove(timedStorageIndex[key]);
  410. timedStorageIndex.Remove(key);
  411. }
  412. else
  413. {
  414. return false;
  415. }
  416. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, slidingExpiration);
  417. timedStorage.Add(internalKey, value);
  418. timedStorageIndex.Add(key, internalKey);
  419. return true;
  420. }
  421. finally
  422. {
  423. // Restore lock state
  424. if (lockUpgrade)
  425. readWriteLock.DowngradeFromWriterLock(ref lc);
  426. else
  427. readWriteLock.ReleaseWriterLock();
  428. }
  429. }
  430. public void CopyTo(Array array, int startIndex)
  431. {
  432. // Error checking
  433. if (array == null) { throw new ArgumentNullException("array"); }
  434. if (startIndex < 0) { throw new ArgumentOutOfRangeException("startIndex", "startIndex must be >= 0."); }
  435. if (array.Rank > 1) { throw new ArgumentException("array must be of Rank 1 (one-dimensional)", "array"); }
  436. if (startIndex >= array.Length) { throw new ArgumentException("startIndex must be less than the length of the array.", "startIndex"); }
  437. 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."); }
  438. // Copy the data to the array (in a thread-safe manner)
  439. readWriteLock.AcquireReaderLock(MAX_LOCK_WAIT);
  440. try
  441. {
  442. foreach (object o in timedStorage)
  443. {
  444. array.SetValue(o, startIndex);
  445. startIndex++;
  446. }
  447. }
  448. finally { readWriteLock.ReleaseReaderLock(); }
  449. }
  450. #endregion
  451. #region Private methods
  452. /// <summary>
  453. /// Purges expired objects from the cache. Called automatically by the purge timer.
  454. /// </summary>
  455. private void PurgeCache(object sender, System.Timers.ElapsedEventArgs e)
  456. {
  457. // Note: This implementation runs with low priority. If the cache lock
  458. // is heavily contended (many threads) the purge will take a long time
  459. // to obtain the lock it needs and may never be run.
  460. Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
  461. // Only let one thread purge at once - a buildup could cause a crash
  462. // This could cause the purge to be delayed while there are lots of read/write ops
  463. // happening on the cache
  464. if (!Monitor.TryEnter(isPurging))
  465. return;
  466. try
  467. {
  468. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  469. try
  470. {
  471. List<object> expiredItems = new List<object>();
  472. foreach (TimedCacheKey<TKey> timedKey in timedStorage.Keys)
  473. {
  474. if (timedKey.ExpirationDate < e.SignalTime)
  475. {
  476. // Mark the object for purge
  477. expiredItems.Add(timedKey.Key);
  478. }
  479. else
  480. {
  481. break;
  482. }
  483. }
  484. foreach (TKey key in expiredItems)
  485. {
  486. TimedCacheKey<TKey> timedKey = timedStorageIndex[key];
  487. timedStorageIndex.Remove(timedKey.Key);
  488. timedStorage.Remove(timedKey);
  489. }
  490. }
  491. catch (ApplicationException)
  492. {
  493. // Unable to obtain write lock to the timed cache storage object
  494. }
  495. finally
  496. {
  497. readWriteLock.ReleaseWriterLock();
  498. }
  499. }
  500. finally { Monitor.Exit(isPurging); }
  501. }
  502. #endregion
  503. }
  504. }