PageRenderTime 60ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 1ms

/Aurora/Framework/Utils/ExpiringList.cs

https://bitbucket.org/VirtualReality/aurora-sim
C# | 523 lines | 421 code | 53 blank | 49 comment | 32 complexity | 7ff1444eb163c92e7d3a4e2ed9d46828 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 OpenMetaverse;
  33. using Timer = System.Timers.Timer;
  34. namespace Aurora.Framework
  35. {
  36. #region TimedCacheKey Class
  37. internal class TimedCacheKey<TKey> : IComparable<TKey>
  38. {
  39. private readonly TKey key;
  40. private readonly bool slidingExpiration;
  41. private readonly TimeSpan slidingExpirationWindowSize;
  42. private DateTime expirationDate;
  43. public TimedCacheKey(TKey key, DateTime expirationDate)
  44. {
  45. this.key = key;
  46. this.slidingExpiration = false;
  47. this.expirationDate = expirationDate;
  48. }
  49. public TimedCacheKey(TKey key, TimeSpan slidingExpirationWindowSize)
  50. {
  51. this.key = key;
  52. this.slidingExpiration = true;
  53. this.slidingExpirationWindowSize = slidingExpirationWindowSize;
  54. Accessed();
  55. }
  56. public DateTime ExpirationDate
  57. {
  58. get { return expirationDate; }
  59. }
  60. public TKey Key
  61. {
  62. get { return key; }
  63. }
  64. public bool SlidingExpiration
  65. {
  66. get { return slidingExpiration; }
  67. }
  68. public TimeSpan SlidingExpirationWindowSize
  69. {
  70. get { return slidingExpirationWindowSize; }
  71. }
  72. #region IComparable<TKey> Members
  73. public int CompareTo(TKey other)
  74. {
  75. return key.GetHashCode().CompareTo(other.GetHashCode());
  76. }
  77. #endregion
  78. public void Accessed()
  79. {
  80. if (slidingExpiration)
  81. expirationDate = DateTime.Now.Add(slidingExpirationWindowSize);
  82. }
  83. }
  84. #endregion
  85. /// <summary>
  86. /// List that has an expiring built in
  87. /// </summary>
  88. /// <typeparam name = "TKey"></typeparam>
  89. public sealed class ExpiringList<TKey>
  90. {
  91. private const double CACHE_PURGE_HZ = 1.0;
  92. private const int MAX_LOCK_WAIT = 5000; // milliseconds
  93. #region Private fields
  94. /// <summary>
  95. /// For thread safety
  96. /// </summary>
  97. private readonly object isPurging = new object();
  98. /// <summary>
  99. /// For thread safety
  100. /// </summary>
  101. private readonly object syncRoot = new object();
  102. private readonly List<TimedCacheKey<TKey>> timedStorage = new List<TimedCacheKey<TKey>>();
  103. private readonly Dictionary<TKey, TimedCacheKey<TKey>> timedStorageIndex =
  104. new Dictionary<TKey, TimedCacheKey<TKey>>();
  105. private readonly Timer timer = new Timer(TimeSpan.FromSeconds(CACHE_PURGE_HZ).TotalMilliseconds);
  106. private double DefaultTime;
  107. #endregion
  108. #region Constructor
  109. public ExpiringList()
  110. {
  111. timer.Elapsed += PurgeCache;
  112. timer.Start();
  113. }
  114. #endregion
  115. #region Public methods
  116. public TKey this[int i]
  117. {
  118. get
  119. {
  120. TKey o;
  121. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  122. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  123. try
  124. {
  125. if (timedStorage.Count > i)
  126. {
  127. TimedCacheKey<TKey> tkey = timedStorage[i];
  128. o = tkey.Key;
  129. timedStorage.Remove(tkey);
  130. tkey.Accessed();
  131. timedStorage.Insert(i, tkey);
  132. return o;
  133. }
  134. else
  135. {
  136. throw new ArgumentException("Key not found in the cache");
  137. }
  138. }
  139. finally
  140. {
  141. Monitor.Exit(syncRoot);
  142. }
  143. }
  144. set { AddOrUpdate(value, DefaultTime); }
  145. }
  146. public int Count
  147. {
  148. get { return timedStorage.Count; }
  149. }
  150. public void SetDefaultTime(double time)
  151. {
  152. DefaultTime = time;
  153. }
  154. public bool Add(TKey key, double expirationSeconds)
  155. {
  156. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  157. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  158. try
  159. {
  160. // This is the actual adding of the key
  161. if (timedStorageIndex.ContainsKey(key))
  162. {
  163. return false;
  164. }
  165. else
  166. {
  167. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key,
  168. DateTime.UtcNow +
  169. TimeSpan.FromSeconds(expirationSeconds));
  170. timedStorage.Add(internalKey);
  171. timedStorageIndex.Add(key, internalKey);
  172. return true;
  173. }
  174. }
  175. finally
  176. {
  177. Monitor.Exit(syncRoot);
  178. }
  179. }
  180. public bool Add(TKey key, TimeSpan slidingExpiration)
  181. {
  182. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  183. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  184. try
  185. {
  186. // This is the actual adding of the key
  187. if (timedStorageIndex.ContainsKey(key))
  188. {
  189. return false;
  190. }
  191. else
  192. {
  193. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, slidingExpiration);
  194. timedStorage.Add(internalKey);
  195. timedStorageIndex.Add(key, internalKey);
  196. return true;
  197. }
  198. }
  199. finally
  200. {
  201. Monitor.Exit(syncRoot);
  202. }
  203. }
  204. public bool AddOrUpdate(TKey key, double expirationSeconds)
  205. {
  206. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  207. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  208. try
  209. {
  210. if (Contains(key))
  211. {
  212. Update(key, expirationSeconds);
  213. return false;
  214. }
  215. else
  216. {
  217. Add(key, expirationSeconds);
  218. return true;
  219. }
  220. }
  221. finally
  222. {
  223. Monitor.Exit(syncRoot);
  224. }
  225. }
  226. public bool AddOrUpdate(TKey key, TimeSpan slidingExpiration)
  227. {
  228. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  229. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  230. try
  231. {
  232. if (Contains(key))
  233. {
  234. Update(key, slidingExpiration);
  235. return false;
  236. }
  237. else
  238. {
  239. Add(key, slidingExpiration);
  240. return true;
  241. }
  242. }
  243. finally
  244. {
  245. Monitor.Exit(syncRoot);
  246. }
  247. }
  248. public void Clear()
  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. timedStorage.Clear();
  255. timedStorageIndex.Clear();
  256. }
  257. finally
  258. {
  259. Monitor.Exit(syncRoot);
  260. }
  261. }
  262. public bool Contains(TKey key)
  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. return timedStorageIndex.ContainsKey(key);
  269. }
  270. finally
  271. {
  272. Monitor.Exit(syncRoot);
  273. }
  274. }
  275. public bool Remove(TKey key)
  276. {
  277. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  278. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  279. try
  280. {
  281. if (timedStorageIndex.ContainsKey(key))
  282. {
  283. timedStorage.Remove(timedStorageIndex[key]);
  284. timedStorageIndex.Remove(key);
  285. return true;
  286. }
  287. else
  288. {
  289. return false;
  290. }
  291. }
  292. finally
  293. {
  294. Monitor.Exit(syncRoot);
  295. }
  296. }
  297. public bool Update(TKey key)
  298. {
  299. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  300. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  301. try
  302. {
  303. if (timedStorageIndex.ContainsKey(key))
  304. {
  305. timedStorage.Remove(timedStorageIndex[key]);
  306. timedStorageIndex[key].Accessed();
  307. timedStorage.Add(timedStorageIndex[key]);
  308. return true;
  309. }
  310. else
  311. {
  312. return false;
  313. }
  314. }
  315. finally
  316. {
  317. Monitor.Exit(syncRoot);
  318. }
  319. }
  320. public bool Update(TKey key, double expirationSeconds)
  321. {
  322. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  323. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  324. try
  325. {
  326. if (timedStorageIndex.ContainsKey(key))
  327. {
  328. timedStorage.Remove(timedStorageIndex[key]);
  329. timedStorageIndex.Remove(key);
  330. }
  331. else
  332. {
  333. return false;
  334. }
  335. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key,
  336. DateTime.UtcNow +
  337. TimeSpan.FromSeconds(expirationSeconds));
  338. timedStorage.Add(internalKey);
  339. timedStorageIndex.Add(key, internalKey);
  340. return true;
  341. }
  342. finally
  343. {
  344. Monitor.Exit(syncRoot);
  345. }
  346. }
  347. public bool Update(TKey key, TimeSpan slidingExpiration)
  348. {
  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. if (timedStorageIndex.ContainsKey(key))
  354. {
  355. timedStorage.Remove(timedStorageIndex[key]);
  356. timedStorageIndex.Remove(key);
  357. }
  358. else
  359. {
  360. return false;
  361. }
  362. TimedCacheKey<TKey> internalKey = new TimedCacheKey<TKey>(key, slidingExpiration);
  363. timedStorage.Add(internalKey);
  364. timedStorageIndex.Add(key, internalKey);
  365. return true;
  366. }
  367. finally
  368. {
  369. Monitor.Exit(syncRoot);
  370. }
  371. }
  372. public void CopyTo(Array array, int startIndex)
  373. {
  374. // Error checking
  375. if (array == null)
  376. {
  377. throw new ArgumentNullException("array");
  378. }
  379. if (startIndex < 0)
  380. {
  381. throw new ArgumentOutOfRangeException("startIndex", "startIndex must be >= 0.");
  382. }
  383. if (array.Rank > 1)
  384. {
  385. throw new ArgumentException("array must be of Rank 1 (one-dimensional)", "array");
  386. }
  387. if (startIndex >= array.Length)
  388. {
  389. throw new ArgumentException("startIndex must be less than the length of the array.", "startIndex");
  390. }
  391. if (Count > array.Length - startIndex)
  392. {
  393. throw new ArgumentException(
  394. "There is not enough space from startIndex to the end of the array to accomodate all items in the cache.");
  395. }
  396. // Copy the data to the array (in a thread-safe manner)
  397. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  398. throw new ApplicationException("Lock could not be acquired after " + MAX_LOCK_WAIT + "ms");
  399. try
  400. {
  401. foreach (object o in timedStorage)
  402. {
  403. array.SetValue(o, startIndex);
  404. startIndex++;
  405. }
  406. }
  407. finally
  408. {
  409. Monitor.Exit(syncRoot);
  410. }
  411. }
  412. #endregion
  413. #region Private methods
  414. /// <summary>
  415. /// Purges expired objects from the cache. Called automatically by the purge timer.
  416. /// </summary>
  417. private void PurgeCache(object sender, ElapsedEventArgs e)
  418. {
  419. // Only let one thread purge at once - a buildup could cause a crash
  420. // This could cause the purge to be delayed while there are lots of read/write ops
  421. // happening on the cache
  422. if (!Monitor.TryEnter(isPurging))
  423. return;
  424. DateTime signalTime = DateTime.UtcNow;
  425. try
  426. {
  427. // If we fail to acquire a lock on the synchronization root after MAX_LOCK_WAIT, skip this purge cycle
  428. if (!Monitor.TryEnter(syncRoot, MAX_LOCK_WAIT))
  429. return;
  430. try
  431. {
  432. Lazy<List<object>> expiredItems = new Lazy<List<object>>();
  433. #if (!ISWIN)
  434. foreach (TimedCacheKey<TKey> timedKey in timedStorage)
  435. {
  436. if (timedKey.ExpirationDate < signalTime)
  437. {
  438. // Mark the object for purge
  439. expiredItems.Value.Add(timedKey.Key);
  440. }
  441. }
  442. #else
  443. foreach (TimedCacheKey<TKey> timedKey in timedStorage.Where(timedKey => timedKey.ExpirationDate < signalTime))
  444. {
  445. // Mark the object for purge
  446. expiredItems.Value.Add(timedKey.Key);
  447. }
  448. #endif
  449. if (expiredItems.IsValueCreated)
  450. {
  451. foreach (TimedCacheKey<TKey> timedKey in from TKey key in expiredItems.Value select timedStorageIndex[key])
  452. {
  453. timedStorageIndex.Remove(timedKey.Key);
  454. timedStorage.Remove(timedKey);
  455. }
  456. }
  457. }
  458. finally
  459. {
  460. Monitor.Exit(syncRoot);
  461. }
  462. }
  463. finally
  464. {
  465. Monitor.Exit(isPurging);
  466. }
  467. }
  468. #endregion
  469. }
  470. }