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

/mcs/class/System.Web/System.Web.Caching/Cache.cs

https://bitbucket.org/danipen/mono
C# | 635 lines | 466 code | 95 blank | 74 comment | 128 complexity | 7e49e93d8085a5e6a42252df496b34c0 MD5 | raw file
Possible License(s): Unlicense, Apache-2.0, LGPL-2.0, MPL-2.0-no-copyleft-exception, CC-BY-SA-3.0, GPL-2.0
  1. //
  2. // System.Web.Caching.Cache
  3. //
  4. // Author(s):
  5. // Lluis Sanchez (lluis@ximian.com)
  6. // Marek Habersack <mhabersack@novell.com>
  7. //
  8. // (C) 2005-2009 Novell, Inc (http://novell.com)
  9. //
  10. //
  11. // Permission is hereby granted, free of charge, to any person obtaining
  12. // a copy of this software and associated documentation files (the
  13. // "Software"), to deal in the Software without restriction, including
  14. // without limitation the rights to use, copy, modify, merge, publish,
  15. // distribute, sublicense, and/or sell copies of the Software, and to
  16. // permit persons to whom the Software is furnished to do so, subject to
  17. // the following conditions:
  18. //
  19. // The above copyright notice and this permission notice shall be
  20. // included in all copies or substantial portions of the Software.
  21. //
  22. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
  23. // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
  24. // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
  25. // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
  26. // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
  27. // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
  28. // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
  29. //
  30. using System.Threading;
  31. using System.Collections;
  32. using System.Collections.Generic;
  33. using System.Linq;
  34. using System.Security.Permissions;
  35. using System.Web.Configuration;
  36. namespace System.Web.Caching
  37. {
  38. // CAS - no InheritanceDemand here as the class is sealed
  39. [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
  40. public sealed class Cache: IEnumerable
  41. {
  42. const int LOW_WATER_MARK = 10000; // Target number of items if high water mark is reached
  43. const int HIGH_WATER_MARK = 15000; // We start collection after exceeding this count
  44. public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue;
  45. public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero;
  46. // cacheLock will be released in the code below without checking whether it was
  47. // actually acquired. The API doesn't offer a reliable way to check whether the lock
  48. // is being held by the current thread and since Mono does't implement CER
  49. // (Constrained Execution Regions -
  50. // http://msdn.microsoft.com/en-us/library/ms228973.aspx) currently, we have no
  51. // reliable way of recording the information that the lock has been successfully
  52. // acquired.
  53. // It can happen that a Thread.Abort occurs while acquiring the lock and the lock
  54. // isn't actually held. In this case the attempt to release a lock will throw an
  55. // exception. It's better than a race of setting a boolean flag after acquiring the
  56. // lock and then relying upon it here to release it - that may cause a deadlock
  57. // should we fail to release the lock which was successfully acquired but
  58. // Thread.Abort happened right after that during the stloc instruction to set the
  59. // boolean flag. Once CERs are supported we can use the boolean flag reliably.
  60. ReaderWriterLockSlim cacheLock;
  61. CacheItemLRU cache;
  62. CacheItemPriorityQueue timedItems;
  63. Timer expirationTimer;
  64. long expirationTimerPeriod = 0;
  65. Cache dependencyCache;
  66. bool? disableExpiration;
  67. long privateBytesLimit = -1;
  68. long percentagePhysicalMemoryLimit = -1;
  69. bool DisableExpiration {
  70. get {
  71. if (disableExpiration == null) {
  72. var cs = WebConfigurationManager.GetWebApplicationSection ("system.web/caching/cache") as CacheSection;
  73. if (cs == null)
  74. disableExpiration = false;
  75. else
  76. disableExpiration = (bool)cs.DisableExpiration;
  77. }
  78. return (bool)disableExpiration;
  79. }
  80. }
  81. public long EffectivePrivateBytesLimit {
  82. get {
  83. if (privateBytesLimit == -1) {
  84. var cs = WebConfigurationManager.GetWebApplicationSection ("system.web/caching/cache") as CacheSection;
  85. if (cs == null)
  86. privateBytesLimit = 0;
  87. else
  88. privateBytesLimit = cs.PrivateBytesLimit;
  89. if (privateBytesLimit == 0) {
  90. // http://blogs.msdn.com/tmarq/archive/2007/06/25/some-history-on-the-asp-net-cache-memory-limits.aspx
  91. // TODO: calculate
  92. privateBytesLimit = 734003200;
  93. }
  94. }
  95. return privateBytesLimit;
  96. }
  97. }
  98. public long EffectivePercentagePhysicalMemoryLimit {
  99. get {
  100. if (percentagePhysicalMemoryLimit == -1) {
  101. var cs = WebConfigurationManager.GetWebApplicationSection ("system.web/caching/cache") as CacheSection;
  102. if (cs == null)
  103. percentagePhysicalMemoryLimit = 0;
  104. else
  105. percentagePhysicalMemoryLimit = cs.PercentagePhysicalMemoryUsedLimit;
  106. if (percentagePhysicalMemoryLimit == 0) {
  107. // http://blogs.msdn.com/tmarq/archive/2007/06/25/some-history-on-the-asp-net-cache-memory-limits.aspx
  108. // TODO: calculate
  109. percentagePhysicalMemoryLimit = 97;
  110. }
  111. }
  112. return percentagePhysicalMemoryLimit;
  113. }
  114. }
  115. public Cache ()
  116. {
  117. cacheLock = new ReaderWriterLockSlim ();
  118. cache = new CacheItemLRU (this, HIGH_WATER_MARK, LOW_WATER_MARK);
  119. }
  120. public int Count {
  121. get { return cache.Count; }
  122. }
  123. public object this [string key] {
  124. get { return Get (key); }
  125. set { Insert (key, value); }
  126. }
  127. // Must ALWAYS be called with the cache write lock held
  128. CacheItem RemoveCacheItem (string key)
  129. {
  130. if (key == null)
  131. return null;
  132. CacheItem ret = cache [key];
  133. if (ret == null)
  134. return null;
  135. if (timedItems != null)
  136. timedItems.OnItemDisable (ret);
  137. ret.Disabled = true;
  138. cache.Remove (key);
  139. return ret;
  140. }
  141. public object Add (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
  142. {
  143. if (key == null)
  144. throw new ArgumentNullException ("key");
  145. try {
  146. cacheLock.EnterWriteLock ();
  147. CacheItem it = cache [key];
  148. if (it != null)
  149. return it.Value;
  150. Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback, null, false);
  151. } finally {
  152. // See comment at the top of the file, above cacheLock declaration
  153. cacheLock.ExitWriteLock ();
  154. }
  155. return null;
  156. }
  157. public object Get (string key)
  158. {
  159. try {
  160. cacheLock.EnterUpgradeableReadLock ();
  161. CacheItem it = cache [key];
  162. if (it == null)
  163. return null;
  164. if (it.Dependency != null && it.Dependency.HasChanged) {
  165. try {
  166. cacheLock.EnterWriteLock ();
  167. if (!NeedsUpdate (it, CacheItemUpdateReason.DependencyChanged, false))
  168. Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false, true);
  169. } finally {
  170. // See comment at the top of the file, above cacheLock declaration
  171. cacheLock.ExitWriteLock ();
  172. }
  173. return null;
  174. }
  175. if (!DisableExpiration) {
  176. if (it.SlidingExpiration != NoSlidingExpiration) {
  177. it.AbsoluteExpiration = DateTime.Now + it.SlidingExpiration;
  178. // Cast to long is ok since we know that sliding expiration
  179. // is less than 365 days (31536000000ms)
  180. long remaining = (long)it.SlidingExpiration.TotalMilliseconds;
  181. it.ExpiresAt = it.AbsoluteExpiration.Ticks;
  182. if (expirationTimer != null && (expirationTimerPeriod == 0 || expirationTimerPeriod > remaining)) {
  183. expirationTimerPeriod = remaining;
  184. expirationTimer.Change (expirationTimerPeriod, expirationTimerPeriod);
  185. }
  186. } else if (DateTime.Now >= it.AbsoluteExpiration) {
  187. try {
  188. cacheLock.EnterWriteLock ();
  189. if (!NeedsUpdate (it, CacheItemUpdateReason.Expired, false))
  190. Remove (key, CacheItemRemovedReason.Expired, false, true);
  191. } finally {
  192. // See comment at the top of the file, above cacheLock declaration
  193. cacheLock.ExitWriteLock ();
  194. }
  195. return null;
  196. }
  197. }
  198. return it.Value;
  199. } finally {
  200. // See comment at the top of the file, above cacheLock declaration
  201. cacheLock.ExitUpgradeableReadLock ();
  202. }
  203. }
  204. public void Insert (string key, object value)
  205. {
  206. Insert (key, value, null, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Normal, null, null, true);
  207. }
  208. public void Insert (string key, object value, CacheDependency dependencies)
  209. {
  210. Insert (key, value, dependencies, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Normal, null, null, true);
  211. }
  212. public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration)
  213. {
  214. Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, CacheItemPriority.Normal, null, null, true);
  215. }
  216. public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
  217. CacheItemUpdateCallback onUpdateCallback)
  218. {
  219. Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, CacheItemPriority.Normal, null, onUpdateCallback, true);
  220. }
  221. public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
  222. CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
  223. {
  224. Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback, null, true);
  225. }
  226. void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
  227. CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback, CacheItemUpdateCallback onUpdateCallback, bool doLock)
  228. {
  229. if (key == null)
  230. throw new ArgumentNullException ("key");
  231. if (value == null)
  232. throw new ArgumentNullException ("value");
  233. if (slidingExpiration < TimeSpan.Zero || slidingExpiration > TimeSpan.FromDays (365))
  234. throw new ArgumentNullException ("slidingExpiration");
  235. if (absoluteExpiration != NoAbsoluteExpiration && slidingExpiration != NoSlidingExpiration)
  236. throw new ArgumentException ("Both absoluteExpiration and slidingExpiration are specified");
  237. CacheItem ci = new CacheItem ();
  238. ci.Value = value;
  239. ci.Key = key;
  240. if (dependencies != null) {
  241. ci.Dependency = dependencies;
  242. dependencies.DependencyChanged += new EventHandler (OnDependencyChanged);
  243. dependencies.SetCache (DependencyCache);
  244. }
  245. ci.Priority = priority;
  246. SetItemTimeout (ci, absoluteExpiration, slidingExpiration, onRemoveCallback, onUpdateCallback, key, doLock);
  247. }
  248. internal void SetItemTimeout (string key, DateTime absoluteExpiration, TimeSpan slidingExpiration, bool doLock)
  249. {
  250. CacheItem ci = null;
  251. try {
  252. if (doLock)
  253. cacheLock.EnterWriteLock ();
  254. ci = cache [key];
  255. if (ci != null)
  256. SetItemTimeout (ci, absoluteExpiration, slidingExpiration, ci.OnRemoveCallback, null, key, false);
  257. } finally {
  258. if (doLock) {
  259. // See comment at the top of the file, above cacheLock declaration
  260. cacheLock.ExitWriteLock ();
  261. }
  262. }
  263. }
  264. void SetItemTimeout (CacheItem ci, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemRemovedCallback onRemoveCallback,
  265. CacheItemUpdateCallback onUpdateCallback, string key, bool doLock)
  266. {
  267. bool disableExpiration = DisableExpiration;
  268. if (!disableExpiration) {
  269. ci.SlidingExpiration = slidingExpiration;
  270. if (slidingExpiration != NoSlidingExpiration)
  271. ci.AbsoluteExpiration = DateTime.Now + slidingExpiration;
  272. else
  273. ci.AbsoluteExpiration = absoluteExpiration;
  274. }
  275. ci.OnRemoveCallback = onRemoveCallback;
  276. ci.OnUpdateCallback = onUpdateCallback;
  277. try {
  278. if (doLock)
  279. cacheLock.EnterWriteLock ();
  280. if (key != null) {
  281. cache [key] = ci;
  282. cache.EvictIfNecessary ();
  283. }
  284. ci.LastChange = DateTime.Now;
  285. if (!disableExpiration && ci.AbsoluteExpiration != NoAbsoluteExpiration) {
  286. bool enqueue;
  287. if (ci.IsTimedItem) {
  288. enqueue = UpdateTimedItem (ci);
  289. if (!enqueue)
  290. UpdateTimerPeriod (ci);
  291. } else
  292. enqueue = true;
  293. if (enqueue) {
  294. ci.IsTimedItem = true;
  295. EnqueueTimedItem (ci);
  296. }
  297. }
  298. } finally {
  299. if (doLock) {
  300. // See comment at the top of the file, above cacheLock declaration
  301. cacheLock.ExitWriteLock ();
  302. }
  303. }
  304. }
  305. // MUST be called with cache lock held
  306. bool UpdateTimedItem (CacheItem item)
  307. {
  308. if (timedItems == null)
  309. return true;
  310. item.ExpiresAt = item.AbsoluteExpiration.Ticks;
  311. return !timedItems.Update (item);
  312. }
  313. // MUST be called with cache lock held
  314. void UpdateTimerPeriod (CacheItem item)
  315. {
  316. if (timedItems == null)
  317. timedItems = new CacheItemPriorityQueue ();
  318. long remaining = Math.Max (0, (long)(item.AbsoluteExpiration - DateTime.Now).TotalMilliseconds);
  319. item.ExpiresAt = item.AbsoluteExpiration.Ticks;
  320. if (remaining > 4294967294)
  321. // Maximum due time for timer
  322. // Item will expire properly anyway, as the timer will be
  323. // rescheduled for the item's expiration time once that item is
  324. // bubbled to the top of the priority queue.
  325. remaining = 4294967294;
  326. if (expirationTimer != null && expirationTimerPeriod <= remaining)
  327. return;
  328. expirationTimerPeriod = remaining;
  329. if (expirationTimer == null)
  330. expirationTimer = new Timer (new TimerCallback (ExpireItems), null, expirationTimerPeriod, expirationTimerPeriod);
  331. else
  332. expirationTimer.Change (expirationTimerPeriod, expirationTimerPeriod);
  333. }
  334. // MUST be called with cache lock held
  335. void EnqueueTimedItem (CacheItem item)
  336. {
  337. UpdateTimerPeriod (item);
  338. timedItems.Enqueue (item);
  339. }
  340. public object Remove (string key)
  341. {
  342. return Remove (key, CacheItemRemovedReason.Removed, true, true);
  343. }
  344. internal object Remove (string key, CacheItemRemovedReason reason, bool doLock, bool invokeCallback)
  345. {
  346. CacheItem it = null;
  347. try {
  348. if (doLock)
  349. cacheLock.EnterWriteLock ();
  350. it = RemoveCacheItem (key);
  351. } finally {
  352. if (doLock) {
  353. // See comment at the top of the file, above cacheLock declaration
  354. cacheLock.ExitWriteLock ();
  355. }
  356. }
  357. object ret = null;
  358. if (it != null) {
  359. if (it.Dependency != null) {
  360. it.Dependency.SetCache (null);
  361. it.Dependency.DependencyChanged -= new EventHandler (OnDependencyChanged);
  362. it.Dependency.Dispose ();
  363. }
  364. if (invokeCallback && it.OnRemoveCallback != null) {
  365. try {
  366. it.OnRemoveCallback (key, it.Value, reason);
  367. } catch {
  368. //TODO: anything to be done here?
  369. }
  370. }
  371. ret = it.Value;
  372. it.Value = null;
  373. it.Key = null;
  374. it.Dependency = null;
  375. it.OnRemoveCallback = null;
  376. it.OnUpdateCallback = null;
  377. it = null;
  378. }
  379. return ret;
  380. }
  381. // Used when shutting down the application so that
  382. // session_end events are sent for all sessions.
  383. internal void InvokePrivateCallbacks ()
  384. {
  385. try {
  386. cacheLock.EnterReadLock ();
  387. cache.InvokePrivateCallbacks ();
  388. } finally {
  389. // See comment at the top of the file, above cacheLock declaration
  390. cacheLock.ExitReadLock ();
  391. }
  392. }
  393. public IDictionaryEnumerator GetEnumerator ()
  394. {
  395. List <CacheItem> list = null;
  396. try {
  397. cacheLock.EnterReadLock ();
  398. list = cache.ToList ();
  399. } finally {
  400. // See comment at the top of the file, above cacheLock declaration
  401. cacheLock.ExitReadLock ();
  402. }
  403. return new CacheItemEnumerator (list);
  404. }
  405. IEnumerator IEnumerable.GetEnumerator ()
  406. {
  407. return GetEnumerator ();
  408. }
  409. void OnDependencyChanged (object o, EventArgs a)
  410. {
  411. CheckDependencies ();
  412. }
  413. bool NeedsUpdate (CacheItem item, CacheItemUpdateReason reason, bool needLock)
  414. {
  415. try {
  416. if (needLock)
  417. cacheLock.EnterWriteLock ();
  418. if (item == null || item.OnUpdateCallback == null)
  419. return false;
  420. object expensiveObject;
  421. CacheDependency dependency;
  422. DateTime absoluteExpiration;
  423. TimeSpan slidingExpiration;
  424. string key = item.Key;
  425. CacheItemUpdateCallback updateCB = item.OnUpdateCallback;
  426. updateCB (key, reason, out expensiveObject, out dependency, out absoluteExpiration, out slidingExpiration);
  427. if (expensiveObject == null)
  428. return false;
  429. CacheItemPriority priority = item.Priority;
  430. CacheItemRemovedCallback removeCB = item.OnRemoveCallback;
  431. CacheItemRemovedReason whyRemoved;
  432. switch (reason) {
  433. case CacheItemUpdateReason.Expired:
  434. whyRemoved = CacheItemRemovedReason.Expired;
  435. break;
  436. case CacheItemUpdateReason.DependencyChanged:
  437. whyRemoved = CacheItemRemovedReason.DependencyChanged;
  438. break;
  439. default:
  440. whyRemoved = CacheItemRemovedReason.Removed;
  441. break;
  442. }
  443. Remove (key, whyRemoved, false, false);
  444. Insert (key, expensiveObject, dependency, absoluteExpiration, slidingExpiration, priority, removeCB, updateCB, false);
  445. return true;
  446. } catch (Exception) {
  447. return false;
  448. } finally {
  449. if (needLock) {
  450. // See comment at the top of the file, above cacheLock declaration
  451. cacheLock.ExitWriteLock ();
  452. }
  453. }
  454. }
  455. void ExpireItems (object data)
  456. {
  457. DateTime now = DateTime.Now;
  458. CacheItem item = null;
  459. expirationTimer.Change (Timeout.Infinite, Timeout.Infinite);
  460. try {
  461. cacheLock.EnterWriteLock ();
  462. while (true) {
  463. item = timedItems.Peek ();
  464. if (item == null) {
  465. if (timedItems.Count == 0)
  466. break;
  467. timedItems.Dequeue ();
  468. continue;
  469. }
  470. if (!item.Disabled && item.ExpiresAt > now.Ticks)
  471. break;
  472. if (item.Disabled) {
  473. item = timedItems.Dequeue ();
  474. continue;
  475. }
  476. item = timedItems.Dequeue ();
  477. if (item != null)
  478. if (!NeedsUpdate (item, CacheItemUpdateReason.Expired, false))
  479. Remove (item.Key, CacheItemRemovedReason.Expired, false, true);
  480. }
  481. } finally {
  482. // See comment at the top of the file, above cacheLock declaration
  483. cacheLock.ExitWriteLock ();
  484. }
  485. if (item != null) {
  486. long remaining = Math.Max (0, (long)(item.AbsoluteExpiration - now).TotalMilliseconds);
  487. if (remaining > 0 && (expirationTimerPeriod == 0 || expirationTimerPeriod > remaining)) {
  488. expirationTimerPeriod = remaining;
  489. expirationTimer.Change (expirationTimerPeriod, expirationTimerPeriod);
  490. return;
  491. }
  492. if (expirationTimerPeriod > 0)
  493. return;
  494. }
  495. expirationTimer.Change (Timeout.Infinite, Timeout.Infinite);
  496. expirationTimerPeriod = 0;
  497. }
  498. internal void CheckDependencies ()
  499. {
  500. try {
  501. cacheLock.EnterWriteLock ();
  502. List <CacheItem> list = cache.SelectItems (it => {
  503. if (it == null)
  504. return false;
  505. if (it.Dependency != null && it.Dependency.HasChanged && !NeedsUpdate (it, CacheItemUpdateReason.DependencyChanged, false))
  506. return true;
  507. return false;
  508. });
  509. foreach (CacheItem it in list)
  510. Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false, true);
  511. list.Clear ();
  512. list.TrimExcess ();
  513. } finally {
  514. // See comment at the top of the file, above cacheLock declaration
  515. cacheLock.ExitWriteLock ();
  516. }
  517. }
  518. internal DateTime GetKeyLastChange (string key)
  519. {
  520. try {
  521. cacheLock.EnterReadLock ();
  522. CacheItem it = cache [key];
  523. if (it == null)
  524. return DateTime.MaxValue;
  525. return it.LastChange;
  526. } finally {
  527. // See comment at the top of the file, above cacheLock declaration
  528. cacheLock.ExitReadLock ();
  529. }
  530. }
  531. internal Cache DependencyCache {
  532. get {
  533. if (dependencyCache == null)
  534. return this;
  535. return dependencyCache;
  536. }
  537. set { dependencyCache = value; }
  538. }
  539. }
  540. }