PageRenderTime 53ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 1ms

/trunk/managed/libomv/libomv-0.7.0/OpenMetaverseTypes/ExpiringCache.cs

https://bitbucket.org/KyanhaLLC/opensim-libs
C# | 453 lines | 367 code | 47 blank | 39 comment | 33 complexity | 1aef08923d931f0ab2b8235b0b5a1802 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. 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, DateTime expiration)
  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, expiration);
  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, DateTime expiration)
  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, expiration);
  138. return false;
  139. }
  140. else
  141. {
  142. Add(key, value, expiration);
  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 TValue this[TKey key, DateTime expiration]
  222. {
  223. set
  224. {
  225. AddOrUpdate(key, value, expiration);
  226. }
  227. }
  228. public TValue this[TKey key, TimeSpan slidingExpiration]
  229. {
  230. set
  231. {
  232. AddOrUpdate(key, value, slidingExpiration);
  233. }
  234. }
  235. public bool Remove(TKey key)
  236. {
  237. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  238. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  239. try
  240. {
  241. if (timedStorageIndex.ContainsKey(key))
  242. {
  243. timedStorage.Remove(timedStorageIndex[key]);
  244. timedStorageIndex.Remove(key);
  245. return true;
  246. }
  247. else
  248. {
  249. return false;
  250. }
  251. }
  252. finally { Monitor.Exit(syncRoot); }
  253. }
  254. public bool TryGetValue(TKey key, out TValue value)
  255. {
  256. TValue o;
  257. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  258. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  259. try
  260. {
  261. if (timedStorageIndex.ContainsKey(key))
  262. {
  263. TimedCacheKey<TKey> tkey = timedStorageIndex[key];
  264. o = timedStorage[tkey];
  265. timedStorage.Remove(tkey);
  266. tkey.Accessed();
  267. timedStorage.Add(tkey, o);
  268. value = o;
  269. return true;
  270. }
  271. }
  272. finally { Monitor.Exit(syncRoot); }
  273. value = default(TValue);
  274. return false;
  275. }
  276. public bool Update(TKey key, TValue value)
  277. {
  278. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  279. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  280. try
  281. {
  282. if (timedStorageIndex.ContainsKey(key))
  283. {
  284. timedStorage.Remove(timedStorageIndex[key]);
  285. timedStorageIndex[key].Accessed();
  286. timedStorage.Add(timedStorageIndex[key], value);
  287. return true;
  288. }
  289. else
  290. {
  291. return false;
  292. }
  293. }
  294. finally { Monitor.Exit(syncRoot); }
  295. }
  296. public bool Update(TKey key, TValue value, DateTime expiration)
  297. {
  298. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  299. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  300. try
  301. {
  302. if (timedStorageIndex.ContainsKey(key))
  303. {
  304. timedStorage.Remove(timedStorageIndex[key]);
  305. timedStorageIndex.Remove(key);
  306. }
  307. else
  308. {
  309. return false;
  310. }
  311. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, expiration);
  312. timedStorage.Add(internalKey, value);
  313. timedStorageIndex.Add(key, internalKey);
  314. return true;
  315. }
  316. finally { Monitor.Exit(syncRoot); }
  317. }
  318. public bool Update(TKey key, TValue value, TimeSpan slidingExpiration)
  319. {
  320. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  321. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  322. try
  323. {
  324. if (timedStorageIndex.ContainsKey(key))
  325. {
  326. timedStorage.Remove(timedStorageIndex[key]);
  327. timedStorageIndex.Remove(key);
  328. }
  329. else
  330. {
  331. return false;
  332. }
  333. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, slidingExpiration);
  334. timedStorage.Add(internalKey, value);
  335. timedStorageIndex.Add(key, internalKey);
  336. return true;
  337. }
  338. finally { Monitor.Exit(syncRoot); }
  339. }
  340. public void CopyTo(Array array, int startIndex)
  341. {
  342. // Error checking
  343. if (array == null) { throw new ArgumentNullException("array"); }
  344. if (startIndex < 0) { throw new ArgumentOutOfRangeException("startIndex", "startIndex must be >= 0."); }
  345. if (array.Rank > 1) { throw new ArgumentException("array must be of Rank 1 (one-dimensional)", "array"); }
  346. if (startIndex >= array.Length) { throw new ArgumentException("startIndex must be less than the length of the array.", "startIndex"); }
  347. 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."); }
  348. // Copy the data to the array (in a thread-safe manner)
  349. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  350. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  351. try
  352. {
  353. foreach (object o in timedStorage)
  354. {
  355. array.SetValue(o, startIndex);
  356. startIndex++;
  357. }
  358. }
  359. finally { Monitor.Exit(syncRoot); }
  360. }
  361. #endregion
  362. #region Private methods
  363. /// <summary>
  364. /// Purges expired objects from the cache. Called automatically by the purge timer.
  365. /// </summary>
  366. private void PurgeCache(object sender, System.Timers.ElapsedEventArgs e)
  367. {
  368. // Only let one thread purge at once - a buildup could cause a crash
  369. // This could cause the purge to be delayed while there are lots of read/write ops
  370. // happening on the cache
  371. if (!Monitor.TryEnter(isPurging))
  372. return;
  373. try
  374. {
  375. // If we fail to acquire a lock on the synchronization root after MAX_LOCK_WAIT, skip this purge cycle
  376. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  377. return;
  378. try
  379. {
  380. List<object> expiredItems = new List<object>();
  381. foreach (TimedCacheKey<TKey> timedKey in timedStorage.Keys)
  382. {
  383. if (timedKey.ExpirationDate < e.SignalTime)
  384. {
  385. // Mark the object for purge
  386. expiredItems.Add(timedKey.Key);
  387. }
  388. else
  389. {
  390. break;
  391. }
  392. }
  393. foreach (TKey key in expiredItems)
  394. {
  395. TimedCacheKey<TKey> timedKey = timedStorageIndex[key];
  396. timedStorageIndex.Remove(timedKey.Key);
  397. timedStorage.Remove(timedKey);
  398. }
  399. }
  400. finally { Monitor.Exit(syncRoot); }
  401. }
  402. finally { Monitor.Exit(isPurging); }
  403. }
  404. #endregion
  405. }
  406. }