PageRenderTime 38ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/OpenMetaverseTypes/Modules/ExpiringCache.cs

https://bitbucket.org/VirtualReality/3rdparty-addon-modules
C# | 443 lines | 357 code | 47 blank | 39 comment | 34 complexity | 116e27e3b79f7fd951a3cd8664d6291a MD5 | raw file
  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. const double CACHE_PURGE_HZ = 1.0;
  69. const int MAX_LOCK_WAIT = 5000; // milliseconds
  70. #region Private fields
  71. /// <summary>For thread safety</summary>
  72. object syncRoot = new object();
  73. /// <summary>For thread safety</summary>
  74. object isPurging = new object();
  75. Dictionary<TimedCacheKey<TKey>, TValue> timedStorage = new Dictionary<TimedCacheKey<TKey>, TValue>();
  76. Dictionary<TKey, TimedCacheKey<TKey>> timedStorageIndex = new Dictionary<TKey, TimedCacheKey<TKey>>();
  77. private System.Timers.Timer timer = new System.Timers.Timer(TimeSpan.FromSeconds(CACHE_PURGE_HZ).TotalMilliseconds);
  78. #endregion
  79. #region Constructor
  80. public ExpiringCache()
  81. {
  82. timer.Elapsed += PurgeCache;
  83. timer.Start();
  84. }
  85. #endregion
  86. #region Public methods
  87. public bool Add(TKey key, TValue value, double expirationSeconds)
  88. {
  89. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  90. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  91. try
  92. {
  93. // This is the actual adding of the key
  94. if (timedStorageIndex.ContainsKey(key))
  95. {
  96. return false;
  97. }
  98. else
  99. {
  100. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, DateTime.UtcNow + TimeSpan.FromSeconds(expirationSeconds));
  101. timedStorage.Add(internalKey, value);
  102. timedStorageIndex.Add(key, internalKey);
  103. return true;
  104. }
  105. }
  106. finally { Monitor.Exit(syncRoot); }
  107. }
  108. public bool Add(TKey key, TValue value, TimeSpan slidingExpiration)
  109. {
  110. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  111. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  112. try
  113. {
  114. // This is the actual adding of the key
  115. if (timedStorageIndex.ContainsKey(key))
  116. {
  117. return false;
  118. }
  119. else
  120. {
  121. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, slidingExpiration);
  122. timedStorage.Add(internalKey, value);
  123. timedStorageIndex.Add(key, internalKey);
  124. return true;
  125. }
  126. }
  127. finally { Monitor.Exit(syncRoot); }
  128. }
  129. public bool AddOrUpdate(TKey key, TValue value, double expirationSeconds)
  130. {
  131. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  132. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  133. try
  134. {
  135. if (Contains(key))
  136. {
  137. Update(key, value, expirationSeconds);
  138. return false;
  139. }
  140. else
  141. {
  142. Add(key, value, expirationSeconds);
  143. return true;
  144. }
  145. }
  146. finally { Monitor.Exit(syncRoot); }
  147. }
  148. public bool AddOrUpdate(TKey key, TValue value, TimeSpan slidingExpiration)
  149. {
  150. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  151. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  152. try
  153. {
  154. if (Contains(key))
  155. {
  156. Update(key, value, slidingExpiration);
  157. return false;
  158. }
  159. else
  160. {
  161. Add(key, value, slidingExpiration);
  162. return true;
  163. }
  164. }
  165. finally { Monitor.Exit(syncRoot); }
  166. }
  167. public void Clear()
  168. {
  169. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  170. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  171. try
  172. {
  173. timedStorage.Clear();
  174. timedStorageIndex.Clear();
  175. }
  176. finally { Monitor.Exit(syncRoot); }
  177. }
  178. public bool Contains(TKey key)
  179. {
  180. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  181. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  182. try
  183. {
  184. return timedStorageIndex.ContainsKey(key);
  185. }
  186. finally { Monitor.Exit(syncRoot); }
  187. }
  188. public int Count
  189. {
  190. get
  191. {
  192. return timedStorage.Count;
  193. }
  194. }
  195. public object this[TKey key]
  196. {
  197. get
  198. {
  199. TValue o;
  200. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  201. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  202. try
  203. {
  204. if (timedStorageIndex.ContainsKey(key))
  205. {
  206. TimedCacheKey<TKey> tkey = timedStorageIndex[key];
  207. o = timedStorage[tkey];
  208. timedStorage.Remove(tkey);
  209. tkey.Accessed();
  210. timedStorage.Add(tkey, o);
  211. return o;
  212. }
  213. else
  214. {
  215. throw new ArgumentException("Key not found in the cache");
  216. }
  217. }
  218. finally { Monitor.Exit(syncRoot); }
  219. }
  220. }
  221. public bool Remove(TKey key)
  222. {
  223. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  224. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  225. try
  226. {
  227. if (timedStorageIndex.ContainsKey(key))
  228. {
  229. timedStorage.Remove(timedStorageIndex[key]);
  230. timedStorageIndex.Remove(key);
  231. return true;
  232. }
  233. else
  234. {
  235. return false;
  236. }
  237. }
  238. finally { Monitor.Exit(syncRoot); }
  239. }
  240. public bool TryGetValue(TKey key, out TValue value)
  241. {
  242. TValue o;
  243. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  244. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  245. try
  246. {
  247. if (timedStorageIndex.ContainsKey(key))
  248. {
  249. TimedCacheKey<TKey> tkey = timedStorageIndex[key];
  250. o = timedStorage[tkey];
  251. timedStorage.Remove(tkey);
  252. tkey.Accessed();
  253. timedStorage.Add(tkey, o);
  254. value = o;
  255. return true;
  256. }
  257. }
  258. finally { Monitor.Exit(syncRoot); }
  259. value = default(TValue);
  260. return false;
  261. }
  262. public bool Update(TKey key, TValue value)
  263. {
  264. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  265. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  266. try
  267. {
  268. if (timedStorageIndex.ContainsKey(key))
  269. {
  270. timedStorage.Remove(timedStorageIndex[key]);
  271. timedStorageIndex[key].Accessed();
  272. timedStorage.Add(timedStorageIndex[key], value);
  273. return true;
  274. }
  275. else
  276. {
  277. return false;
  278. }
  279. }
  280. finally { Monitor.Exit(syncRoot); }
  281. }
  282. public bool Update(TKey key, TValue value, double expirationSeconds)
  283. {
  284. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  285. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  286. try
  287. {
  288. if (timedStorageIndex.ContainsKey(key))
  289. {
  290. timedStorage.Remove(timedStorageIndex[key]);
  291. timedStorageIndex.Remove(key);
  292. }
  293. else
  294. {
  295. return false;
  296. }
  297. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, DateTime.UtcNow + TimeSpan.FromSeconds(expirationSeconds));
  298. timedStorage.Add(internalKey, value);
  299. timedStorageIndex.Add(key, internalKey);
  300. return true;
  301. }
  302. finally { Monitor.Exit(syncRoot); }
  303. }
  304. public bool Update(TKey key, TValue value, TimeSpan slidingExpiration)
  305. {
  306. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  307. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  308. try
  309. {
  310. if (timedStorageIndex.ContainsKey(key))
  311. {
  312. timedStorage.Remove(timedStorageIndex[key]);
  313. timedStorageIndex.Remove(key);
  314. }
  315. else
  316. {
  317. return false;
  318. }
  319. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, slidingExpiration);
  320. timedStorage.Add(internalKey, value);
  321. timedStorageIndex.Add(key, internalKey);
  322. return true;
  323. }
  324. finally { Monitor.Exit(syncRoot); }
  325. }
  326. public void CopyTo(Array array, int startIndex)
  327. {
  328. // Error checking
  329. if (array == null) { throw new ArgumentNullException("array"); }
  330. if (startIndex < 0) { throw new ArgumentOutOfRangeException("startIndex", "startIndex must be >= 0."); }
  331. if (array.Rank > 1) { throw new ArgumentException("array must be of Rank 1 (one-dimensional)", "array"); }
  332. if (startIndex >= array.Length) { throw new ArgumentException("startIndex must be less than the length of the array.", "startIndex"); }
  333. 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."); }
  334. // Copy the data to the array (in a thread-safe manner)
  335. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  336. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  337. try
  338. {
  339. foreach (object o in timedStorage)
  340. {
  341. array.SetValue(o, startIndex);
  342. startIndex++;
  343. }
  344. }
  345. finally { Monitor.Exit(syncRoot); }
  346. }
  347. #endregion
  348. #region Private methods
  349. /// <summary>
  350. /// Purges expired objects from the cache. Called automatically by the purge timer.
  351. /// </summary>
  352. private void PurgeCache(object sender, System.Timers.ElapsedEventArgs e)
  353. {
  354. // Only let one thread purge at once - a buildup could cause a crash
  355. // This could cause the purge to be delayed while there are lots of read/write ops
  356. // happening on the cache
  357. if (!Monitor.TryEnter(isPurging))
  358. return;
  359. DateTime signalTime = DateTime.UtcNow;
  360. try
  361. {
  362. // If we fail to acquire a lock on the synchronization root after MAX_LOCK_WAIT, skip this purge cycle
  363. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  364. return;
  365. try
  366. {
  367. Lazy<List<object>> expiredItems = new Lazy<List<object>>();
  368. foreach (TimedCacheKey<TKey> timedKey in timedStorage.Keys)
  369. {
  370. if (timedKey.ExpirationDate < signalTime)
  371. {
  372. // Mark the object for purge
  373. expiredItems.Value.Add(timedKey.Key);
  374. }
  375. else
  376. {
  377. break;
  378. }
  379. }
  380. if (expiredItems.IsValueCreated)
  381. {
  382. foreach (TKey key in expiredItems.Value)
  383. {
  384. TimedCacheKey<TKey> timedKey = timedStorageIndex[key];
  385. timedStorageIndex.Remove(timedKey.Key);
  386. timedStorage.Remove(timedKey);
  387. }
  388. }
  389. }
  390. finally { Monitor.Exit(syncRoot); }
  391. }
  392. finally { Monitor.Exit(isPurging); }
  393. }
  394. #endregion
  395. }
  396. }