PageRenderTime 48ms CodeModel.GetById 20ms RepoModel.GetById 1ms app.codeStats 0ms

/GlynnTucker.Cache/Modules/SimpleMemoryCache.cs

https://bitbucket.org/VirtualReality/3rdparty-addon-modules
C# | 818 lines | 693 code | 61 blank | 64 comment | 53 complexity | 714743ff7bae81aed4603fe0320db464 MD5 | raw file
  1. /*
  2. *
  3. * Written by Darren Wurf for Glynn Tucker Consulting Engineers
  4. * Copyright (C) 2007 Glynn Tucker Consulting Engineers
  5. * See the file COPYING for licensing information.
  6. *
  7. */
  8. using System;
  9. using System.Threading;
  10. using System.Collections;
  11. using System.Collections.Generic;
  12. namespace GlynnTucker.Cache
  13. {
  14. public sealed class SimpleMemoryCache : ICache
  15. {
  16. #region Enumerator classes for the SimpleMemoryCache
  17. internal class EnumeratorJoiner : EnumeratorJoinerBase, IEnumerator
  18. {
  19. internal EnumeratorJoiner(SimpleMemoryCache parent, params IEnumerator[] enumerators) : base (parent.readWriteLock, enumerators)
  20. {
  21. }
  22. }
  23. internal class DictionaryEnumeratorJoiner : EnumeratorJoinerBase, IDictionaryEnumerator
  24. {
  25. private IList<IDictionaryEnumerator> enumerators;
  26. internal DictionaryEnumeratorJoiner(SimpleMemoryCache parent, params IDictionaryEnumerator[] enumerators)
  27. : base(parent.readWriteLock, enumerators)
  28. {
  29. this.enumerators = new List<IDictionaryEnumerator>(enumerators);
  30. }
  31. public override bool MoveNext()
  32. {
  33. rwLock.AcquireReaderLock(MAX_LOCK_WAIT);
  34. try
  35. {
  36. if (currentEnumerator == null)
  37. {
  38. // First run of MoveNext()
  39. currentEnumerator = 0;
  40. enumerators[0].Reset();
  41. }
  42. else if (currentEnumerator == -1)
  43. {
  44. // MoveNext() has gone past the end of the item list.
  45. // The state will remain invalid until Reset() is called.
  46. return false;
  47. }
  48. if (enumerators[(int)currentEnumerator].MoveNext())
  49. {
  50. currentObject = enumerators[(int)currentEnumerator].Entry;
  51. return true;
  52. }
  53. else
  54. {
  55. // We've hit the last item of the current enumerator;
  56. if (currentEnumerator == enumerators.Count - 1)
  57. {
  58. // We're also on the last enumerator. State is now invalid.
  59. currentEnumerator = -1;
  60. currentObject = null;
  61. return false;
  62. }
  63. else
  64. {
  65. currentEnumerator++;
  66. return MoveNext();
  67. }
  68. }
  69. }
  70. finally { rwLock.ReleaseReaderLock(); }
  71. }
  72. public DictionaryEntry Entry
  73. {
  74. get
  75. {
  76. return (DictionaryEntry)currentObject;
  77. }
  78. }
  79. public object Key
  80. {
  81. get
  82. {
  83. return ((DictionaryEntry)currentObject).Key;
  84. }
  85. }
  86. public object Value
  87. {
  88. get
  89. {
  90. return ((DictionaryEntry)currentObject).Value;
  91. }
  92. }
  93. }
  94. internal class CombinedDictionaryEnumerator : IDictionaryEnumerator
  95. {
  96. IDictionaryEnumerator keyEnumerator;
  97. IDictionaryEnumerator valueEnumerator;
  98. public CombinedDictionaryEnumerator(IDictionary keySource, IDictionary valueSource)
  99. {
  100. lock (this)
  101. {
  102. keyEnumerator = keySource.GetEnumerator();
  103. valueEnumerator = valueSource.GetEnumerator();
  104. }
  105. }
  106. public bool MoveNext()
  107. {
  108. lock (this)
  109. {
  110. return keyEnumerator.MoveNext() && valueEnumerator.MoveNext();
  111. }
  112. }
  113. public void Reset()
  114. {
  115. lock (this)
  116. {
  117. keyEnumerator.Reset();
  118. valueEnumerator.Reset();
  119. }
  120. }
  121. public object Key
  122. {
  123. get { return keyEnumerator.Key; }
  124. }
  125. public object Value
  126. {
  127. get { return valueEnumerator.Value; }
  128. }
  129. public DictionaryEntry Entry
  130. {
  131. get { lock (this) { return new DictionaryEntry(keyEnumerator.Key, valueEnumerator.Value); } }
  132. }
  133. public object Current
  134. {
  135. get { return Entry; }
  136. }
  137. }
  138. #endregion
  139. #region Private and internal fields
  140. /// <summary>
  141. /// For thread safety.
  142. /// </summary>
  143. internal ReaderWriterLock readWriteLock = new ReaderWriterLock();
  144. internal const double CACHE_PURGE_HZ = 1.0;
  145. internal const int MAX_LOCK_WAIT = 5000; // milliseconds
  146. internal Hashtable untimedStorage = new Hashtable();
  147. internal SortedDictionary<TimedCacheKey, object> timedStorage = new SortedDictionary<TimedCacheKey, object>();
  148. internal Dictionary<object, TimedCacheKey> timedStorageIndex = new Dictionary<object, TimedCacheKey>();
  149. private System.Timers.Timer timer = new System.Timers.Timer(TimeSpan.FromSeconds(CACHE_PURGE_HZ).TotalMilliseconds);
  150. object isPurging = new object();
  151. #endregion
  152. #region Constructor
  153. public SimpleMemoryCache()
  154. {
  155. timer.Elapsed += new System.Timers.ElapsedEventHandler(PurgeCache);
  156. timer.Start();
  157. }
  158. #endregion
  159. #region Private methods
  160. /// <summary>
  161. /// Purges expired objects from the cache. Called automatically by the purge timer.
  162. /// </summary>
  163. private void PurgeCache(object sender, System.Timers.ElapsedEventArgs e)
  164. {
  165. // Note: This implementation runs with low priority. If the cache lock
  166. // is heavily contended (many threads) the purge will take a long time
  167. // to obtain the lock it needs and may never be run.
  168. System.Threading.Thread.CurrentThread.Priority = ThreadPriority.BelowNormal;
  169. // Only let one thread purge at once - a buildup could cause a crash
  170. // This could cause the purge to be delayed while there are lots of read/write ops
  171. // happening on the cache
  172. if (!Monitor.TryEnter(isPurging))
  173. {
  174. return;
  175. }
  176. try
  177. {
  178. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  179. try
  180. {
  181. List<object> expiredItems = new List<object>();
  182. /*
  183. DateTime startTime = DateTime.Now;
  184. System.Console.WriteLine("Purge " + " started at " + startTime.ToLongTimeString());
  185. */
  186. foreach (TimedCacheKey timedKey in timedStorage.Keys)
  187. {
  188. if (timedKey.ExpirationDate < e.SignalTime)
  189. {
  190. // Mark the object for purge
  191. expiredItems.Add(timedKey.Key);
  192. }
  193. else
  194. {
  195. break;
  196. }
  197. }
  198. foreach (object key in expiredItems)
  199. {
  200. TimedCacheKey timedKey = timedStorageIndex[key];
  201. timedStorageIndex.Remove(timedKey.Key);
  202. timedStorage.Remove(timedKey);
  203. }
  204. /*
  205. DateTime endTime = DateTime.Now;
  206. System.Console.WriteLine("Purge completed at " + endTime.ToLongTimeString());
  207. System.Console.WriteLine("Time taken to complete purge was " + TimeSpan.FromTicks(endTime.Ticks - startTime.Ticks));
  208. */
  209. }
  210. catch (ApplicationException ae)
  211. {
  212. // Unable to obtain write lock to the timed cache storage object
  213. System.Console.WriteLine("Unable to complete cache purge, could not get writer lock.");
  214. }
  215. finally
  216. {
  217. readWriteLock.ReleaseWriterLock();
  218. }
  219. }
  220. finally { Monitor.Exit(isPurging); }
  221. }
  222. #endregion
  223. #region ICache implementation
  224. public bool Add(object key, object value)
  225. {
  226. // Synchronise access to storage structures. A read lock may
  227. // already be acquired before this method is called.
  228. bool LockUpgraded = readWriteLock.IsReaderLockHeld;
  229. LockCookie lc = new LockCookie();
  230. if (LockUpgraded)
  231. {
  232. lc = readWriteLock.UpgradeToWriterLock(MAX_LOCK_WAIT);
  233. }
  234. else
  235. {
  236. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  237. }
  238. try
  239. {
  240. // This is the actual adding of the key
  241. if (untimedStorage.ContainsKey(key))
  242. {
  243. return false;
  244. }
  245. else
  246. {
  247. untimedStorage.Add(key, value);
  248. return true;
  249. }
  250. }
  251. finally
  252. {
  253. // Restore lock state
  254. if (LockUpgraded)
  255. {
  256. readWriteLock.DowngradeFromWriterLock(ref lc);
  257. }
  258. else
  259. {
  260. readWriteLock.ReleaseWriterLock();
  261. }
  262. }
  263. }
  264. public bool Add(object key, object value, DateTime expiration)
  265. {
  266. // Synchronise access to storage structures. A read lock may
  267. // already be acquired before this method is called.
  268. bool LockUpgraded = readWriteLock.IsReaderLockHeld;
  269. LockCookie lc = new LockCookie();
  270. if (LockUpgraded)
  271. {
  272. lc = readWriteLock.UpgradeToWriterLock(MAX_LOCK_WAIT);
  273. }
  274. else
  275. {
  276. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  277. }
  278. try
  279. {
  280. // This is the actual adding of the key
  281. if (timedStorageIndex.ContainsKey(key))
  282. {
  283. return false;
  284. }
  285. else
  286. {
  287. TimedCacheKey internalKey = new TimedCacheKey(key, expiration);
  288. timedStorage.Add(internalKey, value);
  289. timedStorageIndex.Add(key, internalKey);
  290. return true;
  291. }
  292. }
  293. finally
  294. {
  295. // Restore lock state
  296. if (LockUpgraded)
  297. {
  298. readWriteLock.DowngradeFromWriterLock(ref lc);
  299. }
  300. else
  301. {
  302. readWriteLock.ReleaseWriterLock();
  303. }
  304. }
  305. }
  306. public bool Add(object key, object value, TimeSpan slidingExpiration)
  307. {
  308. // Synchronise access to storage structures. A read lock may
  309. // already be acquired before this method is called.
  310. bool LockUpgraded = readWriteLock.IsReaderLockHeld;
  311. LockCookie lc = new LockCookie();
  312. if (LockUpgraded)
  313. {
  314. lc = readWriteLock.UpgradeToWriterLock(MAX_LOCK_WAIT);
  315. }
  316. else
  317. {
  318. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  319. }
  320. try
  321. {
  322. // This is the actual adding of the key
  323. if (timedStorageIndex.ContainsKey(key))
  324. {
  325. return false;
  326. }
  327. else
  328. {
  329. TimedCacheKey internalKey = new TimedCacheKey(key, slidingExpiration);
  330. timedStorage.Add(internalKey, value);
  331. timedStorageIndex.Add(key, internalKey);
  332. return true;
  333. }
  334. }
  335. finally
  336. {
  337. // Restore lock state
  338. if (LockUpgraded)
  339. {
  340. readWriteLock.DowngradeFromWriterLock(ref lc);
  341. }
  342. else
  343. {
  344. readWriteLock.ReleaseWriterLock();
  345. }
  346. }
  347. }
  348. public bool AddOrUpdate(object key, object value)
  349. {
  350. readWriteLock.AcquireReaderLock(MAX_LOCK_WAIT);
  351. try
  352. {
  353. if (Contains(key))
  354. {
  355. Update(key, value);
  356. return false;
  357. }
  358. else
  359. {
  360. Add(key, value);
  361. return true;
  362. }
  363. }
  364. finally { readWriteLock.ReleaseReaderLock(); }
  365. }
  366. public bool AddOrUpdate(object key, object value, DateTime expiration)
  367. {
  368. readWriteLock.AcquireReaderLock(MAX_LOCK_WAIT);
  369. try
  370. {
  371. if (Contains(key))
  372. {
  373. Update(key, value, expiration);
  374. return false;
  375. }
  376. else
  377. {
  378. Add(key, value, expiration);
  379. return true;
  380. }
  381. }
  382. finally { readWriteLock.ReleaseReaderLock(); }
  383. }
  384. public bool AddOrUpdate(object key, object value, TimeSpan slidingExpiration)
  385. {
  386. readWriteLock.AcquireReaderLock(MAX_LOCK_WAIT);
  387. try
  388. {
  389. if (Contains(key))
  390. {
  391. Update(key, value, slidingExpiration);
  392. return false;
  393. }
  394. else
  395. {
  396. Add(key, value, slidingExpiration);
  397. return true;
  398. }
  399. }
  400. finally { readWriteLock.ReleaseReaderLock(); }
  401. }
  402. public void Clear()
  403. {
  404. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  405. try
  406. {
  407. timedStorage.Clear();
  408. timedStorageIndex.Clear();
  409. untimedStorage.Clear();
  410. }
  411. finally { readWriteLock.ReleaseWriterLock(); }
  412. }
  413. public bool Contains(object key)
  414. {
  415. readWriteLock.AcquireReaderLock(MAX_LOCK_WAIT);
  416. try
  417. {
  418. return untimedStorage.ContainsKey(key) || timedStorageIndex.ContainsKey(key);
  419. }
  420. finally { readWriteLock.ReleaseReaderLock(); }
  421. }
  422. public int Count
  423. {
  424. get
  425. {
  426. int theCount = 0;
  427. readWriteLock.AcquireReaderLock(MAX_LOCK_WAIT);
  428. try
  429. {
  430. theCount = untimedStorage.Count + timedStorageIndex.Count;
  431. }
  432. finally { readWriteLock.ReleaseReaderLock(); }
  433. return theCount;
  434. }
  435. }
  436. public object Get(object key)
  437. {
  438. object o;
  439. readWriteLock.AcquireReaderLock(MAX_LOCK_WAIT);
  440. try
  441. {
  442. if (untimedStorage.ContainsKey(key))
  443. {
  444. return untimedStorage[key];
  445. }
  446. LockCookie lc = readWriteLock.UpgradeToWriterLock(MAX_LOCK_WAIT);
  447. try
  448. {
  449. if (timedStorageIndex.ContainsKey(key))
  450. {
  451. TimedCacheKey tkey = timedStorageIndex[key];
  452. o = timedStorage[tkey];
  453. timedStorage.Remove(tkey);
  454. tkey.Accessed();
  455. timedStorage.Add(tkey, o);
  456. return o;
  457. }
  458. else
  459. {
  460. throw new ArgumentException("Key not found in the cache");
  461. }
  462. }
  463. finally { readWriteLock.DowngradeFromWriterLock(ref lc); }
  464. }
  465. finally { readWriteLock.ReleaseReaderLock(); }
  466. }
  467. public object this[object key]
  468. {
  469. get
  470. {
  471. return Get(key);
  472. }
  473. set
  474. {
  475. AddOrUpdate(key, value);
  476. }
  477. }
  478. public object this[object key, DateTime expiration]
  479. {
  480. set
  481. {
  482. AddOrUpdate(key, value, expiration);
  483. }
  484. }
  485. public object this[object key, TimeSpan slidingExpiration]
  486. {
  487. set
  488. {
  489. AddOrUpdate(key, value, slidingExpiration);
  490. }
  491. }
  492. public bool Remove(object key)
  493. {
  494. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  495. try
  496. {
  497. if (untimedStorage.ContainsKey(key))
  498. {
  499. untimedStorage.Remove(key);
  500. return true;
  501. }
  502. else if (timedStorageIndex.ContainsKey(key))
  503. {
  504. timedStorage.Remove(timedStorageIndex[key]);
  505. timedStorageIndex.Remove(key);
  506. return true;
  507. }
  508. else
  509. {
  510. return false;
  511. }
  512. }
  513. finally { readWriteLock.ReleaseWriterLock(); }
  514. }
  515. public bool TryGet(object key, out object value)
  516. {
  517. object o;
  518. readWriteLock.AcquireReaderLock(MAX_LOCK_WAIT);
  519. try
  520. {
  521. if (untimedStorage.ContainsKey(key))
  522. {
  523. value = untimedStorage[key];
  524. return true;
  525. }
  526. LockCookie lc = readWriteLock.UpgradeToWriterLock(MAX_LOCK_WAIT);
  527. try
  528. {
  529. if (timedStorageIndex.ContainsKey(key))
  530. {
  531. TimedCacheKey tkey = timedStorageIndex[key];
  532. o = timedStorage[tkey];
  533. timedStorage.Remove(tkey);
  534. tkey.Accessed();
  535. timedStorage.Add(tkey, o);
  536. value = o;
  537. return true;
  538. }
  539. else
  540. {
  541. value = null;
  542. return false;
  543. }
  544. }
  545. finally { readWriteLock.DowngradeFromWriterLock(ref lc); }
  546. }
  547. finally { readWriteLock.ReleaseReaderLock(); }
  548. }
  549. public bool Update(object key, object value)
  550. {
  551. // Synchronise access to storage structures. A read lock may
  552. // already be acquired before this method is called.
  553. bool LockUpgraded = readWriteLock.IsReaderLockHeld;
  554. LockCookie lc = new LockCookie();
  555. if (LockUpgraded)
  556. {
  557. lc = readWriteLock.UpgradeToWriterLock(MAX_LOCK_WAIT);
  558. }
  559. else
  560. {
  561. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  562. }
  563. try
  564. {
  565. if (untimedStorage.ContainsKey(key))
  566. {
  567. untimedStorage.Remove(key);
  568. untimedStorage.Add(key, value);
  569. return true;
  570. }
  571. else if (timedStorageIndex.ContainsKey(key))
  572. {
  573. timedStorage.Remove(timedStorageIndex[key]);
  574. timedStorageIndex[key].Accessed();
  575. timedStorage.Add(timedStorageIndex[key], value);
  576. return true;
  577. }
  578. else
  579. {
  580. return false;
  581. }
  582. }
  583. finally
  584. {
  585. // Restore lock state
  586. if (LockUpgraded)
  587. {
  588. readWriteLock.DowngradeFromWriterLock(ref lc);
  589. }
  590. else
  591. {
  592. readWriteLock.ReleaseWriterLock();
  593. }
  594. }
  595. }
  596. public bool Update(object key, object value, DateTime expiration)
  597. {
  598. // Synchronise access to storage structures. A read lock may
  599. // already be acquired before this method is called.
  600. bool LockUpgraded = readWriteLock.IsReaderLockHeld;
  601. LockCookie lc = new LockCookie();
  602. if (LockUpgraded)
  603. {
  604. lc = readWriteLock.UpgradeToWriterLock(MAX_LOCK_WAIT);
  605. }
  606. else
  607. {
  608. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  609. }
  610. try
  611. {
  612. if (untimedStorage.ContainsKey(key))
  613. {
  614. untimedStorage.Remove(key);
  615. }
  616. else if (timedStorageIndex.ContainsKey(key))
  617. {
  618. timedStorage.Remove(timedStorageIndex[key]);
  619. timedStorageIndex.Remove(key);
  620. }
  621. else
  622. {
  623. return false;
  624. }
  625. TimedCacheKey internalKey = new TimedCacheKey(key, expiration);
  626. timedStorage.Add(internalKey, value);
  627. timedStorageIndex.Add(key, internalKey);
  628. return true;
  629. }
  630. finally
  631. {
  632. // Restore lock state
  633. if (LockUpgraded)
  634. {
  635. readWriteLock.DowngradeFromWriterLock(ref lc);
  636. }
  637. else
  638. {
  639. readWriteLock.ReleaseWriterLock();
  640. }
  641. }
  642. }
  643. public bool Update(object key, object value, TimeSpan slidingExpiration)
  644. {
  645. // Synchronise access to storage structures. A read lock may
  646. // already be acquired before this method is called.
  647. bool LockUpgraded = readWriteLock.IsReaderLockHeld;
  648. LockCookie lc = new LockCookie();
  649. if (LockUpgraded)
  650. {
  651. lc = readWriteLock.UpgradeToWriterLock(MAX_LOCK_WAIT);
  652. }
  653. else
  654. {
  655. readWriteLock.AcquireWriterLock(MAX_LOCK_WAIT);
  656. }
  657. try
  658. {
  659. if (untimedStorage.ContainsKey(key))
  660. {
  661. untimedStorage.Remove(key);
  662. }
  663. else if (timedStorageIndex.ContainsKey(key))
  664. {
  665. timedStorage.Remove(timedStorageIndex[key]);
  666. timedStorageIndex.Remove(key);
  667. }
  668. else
  669. {
  670. return false;
  671. }
  672. TimedCacheKey internalKey = new TimedCacheKey(key, slidingExpiration);
  673. timedStorage.Add(internalKey, value);
  674. timedStorageIndex.Add(key, internalKey);
  675. return true;
  676. }
  677. finally
  678. {
  679. // Restore lock state
  680. if (LockUpgraded)
  681. {
  682. readWriteLock.DowngradeFromWriterLock(ref lc);
  683. }
  684. else
  685. {
  686. readWriteLock.ReleaseWriterLock();
  687. }
  688. }
  689. }
  690. #endregion
  691. #region IDictionary
  692. public ICollection Keys
  693. {
  694. get
  695. {
  696. // TODO: should we be passing a lock?
  697. return new CollectionJoinerBase(timedStorageIndex.Keys, untimedStorage.Keys);
  698. }
  699. }
  700. public ICollection Values
  701. {
  702. get
  703. {
  704. // TODO: should we be passing a lock?
  705. return new CollectionJoinerBase(timedStorage.Values, untimedStorage.Values);
  706. }
  707. }
  708. // A cache is not read only
  709. public bool IsReadOnly { get { return false; } }
  710. // A cache is not fixed-size
  711. public bool IsFixedSize { get { return false; } }
  712. public void CopyTo(Array array, int startIndex)
  713. {
  714. // Error checking
  715. if (array == null) { throw new ArgumentNullException("array"); }
  716. if (startIndex < 0) { throw new ArgumentOutOfRangeException("startIndex", "startIndex must be >= 0."); }
  717. if (array.Rank > 1) { throw new ArgumentException("array must be of Rank 1 (one-dimensional)", "array"); }
  718. if (startIndex >= array.Length) { throw new ArgumentException("startIndex must be less than the length of the array.", "startIndex"); }
  719. 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."); }
  720. // Copy the data to the array (in a thread-safe manner)
  721. readWriteLock.AcquireReaderLock(MAX_LOCK_WAIT);
  722. try
  723. {
  724. foreach (object o in timedStorage)
  725. {
  726. array.SetValue(o, startIndex);
  727. startIndex++;
  728. }
  729. foreach (object o in untimedStorage)
  730. {
  731. array.SetValue(o, startIndex);
  732. startIndex++;
  733. }
  734. }
  735. finally { readWriteLock.ReleaseReaderLock(); }
  736. }
  737. public object SyncRoot { get { return this;/*prototype implementation. TODO: think on this some more.*/ } }
  738. // This implementation is thread-safe
  739. public bool IsSynchronized { get { return true; } }
  740. public IDictionaryEnumerator GetEnumerator() { return new DictionaryEnumeratorJoiner(this, new CombinedDictionaryEnumerator(timedStorageIndex, timedStorage), untimedStorage.GetEnumerator()); }
  741. // TODO stuff
  742. public void foo()
  743. {
  744. IDictionary d = null; object o;
  745. o = d.SyncRoot; // This one will require some research - read up on Monitor locking
  746. o = d.Keys; // time-consuming
  747. o = d.Values; // time consuming
  748. o = d.GetEnumerator(); // a tad complex!
  749. }
  750. #endregion
  751. #region Suppress redundant IDictionary implementations
  752. void IDictionary.Add(object key, object value) { }
  753. void IDictionary.Remove(object key) { }
  754. IEnumerator IEnumerable.GetEnumerator() { return null; }
  755. #endregion
  756. }
  757. }