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

/Aurora/Framework/Utilities/ExpiringList.cs

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