2 // System.Web.Caching.Cache
5 // Lluis Sanchez (lluis@ximian.com)
6 // Marek Habersack <mhabersack@novell.com>
8 // (C) 2005-2009 Novell, Inc (http://novell.com)
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:
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
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.
31 using System.Threading;
32 using System.Collections;
33 using System.Collections.Generic;
34 using System.Security.Permissions;
35 using System.Web.Configuration;
37 namespace System.Web.Caching
39 // CAS - no InheritanceDemand here as the class is sealed
40 [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
41 public sealed class Cache: IEnumerable
43 public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue;
44 public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero;
45 ReaderWriterLockSlim cacheLock;
46 Dictionary <string, CacheItem> cache;
47 CacheItemPriorityQueue timedItems;
48 Timer expirationTimer;
49 long expirationTimerPeriod = 0;
50 Cache dependencyCache;
51 bool? disableExpiration;
52 long privateBytesLimit = -1;
53 long percentagePhysicalMemoryLimit = -1;
55 bool DisableExpiration {
57 if (disableExpiration == null) {
58 var cs = WebConfigurationManager.GetWebApplicationSection ("system.web/caching/cache") as CacheSection;
60 disableExpiration = false;
62 disableExpiration = (bool)cs.DisableExpiration;
65 return (bool)disableExpiration;
69 public long EffectivePrivateBytesLimit {
71 if (privateBytesLimit == -1) {
72 var cs = WebConfigurationManager.GetWebApplicationSection ("system.web/caching/cache") as CacheSection;
74 privateBytesLimit = 0;
76 privateBytesLimit = cs.PrivateBytesLimit;
78 if (privateBytesLimit == 0) {
79 // http://blogs.msdn.com/tmarq/archive/2007/06/25/some-history-on-the-asp-net-cache-memory-limits.aspx
81 privateBytesLimit = 734003200;
85 return privateBytesLimit;
89 public long EffectivePercentagePhysicalMemoryLimit {
91 if (percentagePhysicalMemoryLimit == -1) {
92 var cs = WebConfigurationManager.GetWebApplicationSection ("system.web/caching/cache") as CacheSection;
94 percentagePhysicalMemoryLimit = 0;
96 percentagePhysicalMemoryLimit = cs.PercentagePhysicalMemoryUsedLimit;
98 if (percentagePhysicalMemoryLimit == 0) {
99 // http://blogs.msdn.com/tmarq/archive/2007/06/25/some-history-on-the-asp-net-cache-memory-limits.aspx
101 percentagePhysicalMemoryLimit = 97;
105 return percentagePhysicalMemoryLimit;
111 cacheLock = new ReaderWriterLockSlim ();
112 cache = new Dictionary <string, CacheItem> (StringComparer.Ordinal);
116 get { return cache.Count; }
119 public object this [string key] {
120 get { return Get (key); }
121 set { Insert (key, value); }
124 CacheItem GetCacheItem (string key)
130 if (cache.TryGetValue (key, out ret))
135 CacheItem RemoveCacheItem (string key)
140 CacheItem ret = null;
141 if (!cache.TryGetValue (key, out ret))
143 if (timedItems != null)
144 timedItems.OnItemDisable (ret);
152 public object Add (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
155 throw new ArgumentNullException ("key");
159 cacheLock.EnterWriteLock ();
161 CacheItem it = GetCacheItem (key);
165 Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback, null, false);
168 cacheLock.ExitWriteLock ();
174 public object Get (string key)
178 cacheLock.EnterUpgradeableReadLock ();
180 CacheItem it = GetCacheItem (key);
184 if (it.Dependency != null && it.Dependency.HasChanged) {
186 cacheLock.EnterWriteLock ();
187 if (!NeedsUpdate (it, CacheItemUpdateReason.DependencyChanged, false))
188 Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false, true);
190 cacheLock.ExitWriteLock ();
196 if (!DisableExpiration) {
197 if (it.SlidingExpiration != NoSlidingExpiration) {
198 it.AbsoluteExpiration = DateTime.Now + it.SlidingExpiration;
199 // Cast to long is ok since we know that sliding expiration
200 // is less than 365 days (31536000000ms)
201 long remaining = (long)it.SlidingExpiration.TotalMilliseconds;
202 it.ExpiresAt = it.AbsoluteExpiration.Ticks;
204 if (expirationTimer != null && (expirationTimerPeriod == 0 || expirationTimerPeriod > remaining)) {
205 expirationTimerPeriod = remaining;
206 expirationTimer.Change (expirationTimerPeriod, expirationTimerPeriod);
209 } else if (DateTime.Now >= it.AbsoluteExpiration) {
211 cacheLock.EnterWriteLock ();
212 if (!NeedsUpdate (it, CacheItemUpdateReason.Expired, false))
213 Remove (key, CacheItemRemovedReason.Expired, false, true);
215 cacheLock.ExitWriteLock ();
225 cacheLock.ExitUpgradeableReadLock ();
230 public void Insert (string key, object value)
232 Insert (key, value, null, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Normal, null, null, true);
235 public void Insert (string key, object value, CacheDependency dependencies)
237 Insert (key, value, dependencies, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Normal, null, null, true);
240 public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration)
242 Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, CacheItemPriority.Normal, null, null, true);
245 public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
246 CacheItemUpdateCallback onUpdateCallback)
248 Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, CacheItemPriority.Normal, null, onUpdateCallback, true);
251 public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
252 CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
254 Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback, null, true);
257 void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
258 CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback, CacheItemUpdateCallback onUpdateCallback, bool doLock)
261 throw new ArgumentNullException ("key");
263 throw new ArgumentNullException ("value");
264 if (slidingExpiration < TimeSpan.Zero || slidingExpiration > TimeSpan.FromDays (365))
265 throw new ArgumentNullException ("slidingExpiration");
266 if (absoluteExpiration != NoAbsoluteExpiration && slidingExpiration != NoSlidingExpiration)
267 throw new ArgumentException ("Both absoluteExpiration and slidingExpiration are specified");
269 CacheItem ci = new CacheItem ();
273 if (dependencies != null) {
274 ci.Dependency = dependencies;
275 dependencies.DependencyChanged += new EventHandler (OnDependencyChanged);
276 dependencies.SetCache (DependencyCache);
279 ci.Priority = priority;
280 SetItemTimeout (ci, absoluteExpiration, slidingExpiration, onRemoveCallback, onUpdateCallback, key, doLock);
283 internal void SetItemTimeout (string key, DateTime absoluteExpiration, TimeSpan slidingExpiration, bool doLock)
290 cacheLock.EnterWriteLock ();
294 ci = GetCacheItem (key);
296 SetItemTimeout (ci, absoluteExpiration, slidingExpiration, ci.OnRemoveCallback, null, key, false);
299 cacheLock.ExitWriteLock ();
304 void SetItemTimeout (CacheItem ci, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemRemovedCallback onRemoveCallback,
305 CacheItemUpdateCallback onUpdateCallback, string key, bool doLock)
307 bool disableExpiration = DisableExpiration;
309 if (!disableExpiration) {
310 ci.SlidingExpiration = slidingExpiration;
311 if (slidingExpiration != NoSlidingExpiration)
312 ci.AbsoluteExpiration = DateTime.Now + slidingExpiration;
314 ci.AbsoluteExpiration = absoluteExpiration;
317 ci.OnRemoveCallback = onRemoveCallback;
318 ci.OnUpdateCallback = onUpdateCallback;
323 cacheLock.EnterWriteLock ();
327 if (ci.Timer != null) {
335 ci.LastChange = DateTime.Now;
336 if (!disableExpiration && ci.AbsoluteExpiration != NoAbsoluteExpiration)
337 EnqueueTimedItem (ci);
340 cacheLock.ExitWriteLock ();
345 // MUST be called with cache lock held
346 void EnqueueTimedItem (CacheItem item)
348 long remaining = Math.Max (0, (long)(item.AbsoluteExpiration - DateTime.Now).TotalMilliseconds);
349 item.ExpiresAt = item.AbsoluteExpiration.Ticks;
351 if (timedItems == null)
352 timedItems = new CacheItemPriorityQueue ();
354 if (remaining > 4294967294)
355 // Maximum due time for timer
356 // Item will expire properly anyway, as the timer will be
357 // rescheduled for the item's expiration time once that item is
358 // bubbled to the top of the priority queue.
359 expirationTimerPeriod = 4294967294;
361 expirationTimerPeriod = remaining;
363 if (expirationTimer == null)
364 expirationTimer = new Timer (new TimerCallback (ExpireItems), null, expirationTimerPeriod, expirationTimerPeriod);
366 expirationTimer.Change (expirationTimerPeriod, expirationTimerPeriod);
368 timedItems.Enqueue (item);
371 public object Remove (string key)
373 return Remove (key, CacheItemRemovedReason.Removed, true, true);
376 object Remove (string key, CacheItemRemovedReason reason, bool doLock, bool invokeCallback)
382 cacheLock.EnterWriteLock ();
386 it = RemoveCacheItem (key);
389 cacheLock.ExitWriteLock ();
398 if (it.Dependency != null) {
399 it.Dependency.SetCache (null);
400 it.Dependency.DependencyChanged -= new EventHandler (OnDependencyChanged);
401 it.Dependency.Dispose ();
403 if (invokeCallback && it.OnRemoveCallback != null) {
405 it.OnRemoveCallback (key, it.Value, reason);
407 //TODO: anything to be done here?
410 object ret = it.Value;
413 it.Dependency = null;
414 it.OnRemoveCallback = null;
415 it.OnUpdateCallback = null;
422 // Used when shutting down the application so that
423 // session_end events are sent for all sessions.
424 internal void InvokePrivateCallbacks ()
426 CacheItemRemovedReason reason = CacheItemRemovedReason.Removed;
429 cacheLock.EnterReadLock ();
431 foreach (string key in cache.Keys) {
432 CacheItem item = GetCacheItem (key);
436 if (item != null && item.OnRemoveCallback != null) {
438 item.OnRemoveCallback (key, item.Value, reason);
440 //TODO: anything to be done here?
446 cacheLock.ExitReadLock ();
451 public IDictionaryEnumerator GetEnumerator ()
453 ArrayList list = new ArrayList ();
456 cacheLock.EnterReadLock ();
458 foreach (CacheItem it in cache.Values)
462 cacheLock.ExitReadLock ();
466 return new CacheItemEnumerator (list);
469 IEnumerator IEnumerable.GetEnumerator ()
471 return GetEnumerator ();
474 void OnDependencyChanged (object o, EventArgs a)
476 CheckDependencies ();
479 bool NeedsUpdate (CacheItem item, CacheItemUpdateReason reason, bool needLock)
485 cacheLock.EnterWriteLock ();
489 if (item == null || item.OnUpdateCallback == null)
492 object expensiveObject;
493 CacheDependency dependency;
494 DateTime absoluteExpiration;
495 TimeSpan slidingExpiration;
496 string key = item.Key;
497 CacheItemUpdateCallback updateCB = item.OnUpdateCallback;
499 updateCB (key, reason, out expensiveObject, out dependency, out absoluteExpiration, out slidingExpiration);
500 if (expensiveObject == null)
503 CacheItemPriority priority = item.Priority;
504 CacheItemRemovedCallback removeCB = item.OnRemoveCallback;
505 CacheItemRemovedReason whyRemoved;
508 case CacheItemUpdateReason.Expired:
509 whyRemoved = CacheItemRemovedReason.Expired;
512 case CacheItemUpdateReason.DependencyChanged:
513 whyRemoved = CacheItemRemovedReason.DependencyChanged;
517 whyRemoved = CacheItemRemovedReason.Removed;
521 Remove (key, whyRemoved, false, false);
522 Insert (key, expensiveObject, dependency, absoluteExpiration, slidingExpiration, priority, removeCB, updateCB, false);
525 } catch (Exception) {
529 cacheLock.ExitWriteLock ();
533 void ExpireItems (object data)
535 DateTime now = DateTime.Now;
536 CacheItem item = timedItems.Peek ();
540 cacheLock.EnterWriteLock ();
543 while (item != null) {
544 if (!item.Disabled && item.ExpiresAt > now.Ticks)
547 item = timedItems.Dequeue ();
551 item = timedItems.Dequeue ();
552 if (!NeedsUpdate (item, CacheItemUpdateReason.Expired, true))
553 Remove (item.Key, CacheItemRemovedReason.Expired, false, true);
554 item = timedItems.Peek ();
558 cacheLock.ExitWriteLock ();
562 long remaining = Math.Max (0, (long)(item.AbsoluteExpiration - now).TotalMilliseconds);
563 if (expirationTimerPeriod != remaining && remaining > 0) {
564 expirationTimerPeriod = remaining;
565 expirationTimer.Change (expirationTimerPeriod, expirationTimerPeriod);
570 expirationTimer.Change (Timeout.Infinite, Timeout.Infinite);
571 expirationTimerPeriod = 0;
574 internal void CheckDependencies ()
579 cacheLock.EnterWriteLock ();
581 list = new List <CacheItem> ();
582 foreach (CacheItem it in cache.Values)
585 foreach (CacheItem it in list) {
586 if (it.Dependency != null && it.Dependency.HasChanged && !NeedsUpdate (it, CacheItemUpdateReason.DependencyChanged, false))
587 Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false, true);
591 cacheLock.ExitWriteLock ();
596 internal DateTime GetKeyLastChange (string key)
600 cacheLock.EnterReadLock ();
602 CacheItem it = GetCacheItem (key);
605 return DateTime.MaxValue;
607 return it.LastChange;
610 cacheLock.ExitReadLock ();
615 internal Cache DependencyCache {
617 if (dependencyCache == null)
620 return dependencyCache;
622 set { dependencyCache = value; }