/mcs/class/System.Web/System.Web.Caching/Cache.cs
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
- //
- // System.Web.Caching.Cache
- //
- // Author(s):
- // Lluis Sanchez (lluis@ximian.com)
- // Marek Habersack <mhabersack@novell.com>
- //
- // (C) 2005-2009 Novell, Inc (http://novell.com)
- //
- //
- // Permission is hereby granted, free of charge, to any person obtaining
- // a copy of this software and associated documentation files (the
- // "Software"), to deal in the Software without restriction, including
- // without limitation the rights to use, copy, modify, merge, publish,
- // distribute, sublicense, and/or sell copies of the Software, and to
- // permit persons to whom the Software is furnished to do so, subject to
- // the following conditions:
- //
- // The above copyright notice and this permission notice shall be
- // included in all copies or substantial portions of the Software.
- //
- // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
- // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
- // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
- // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
- // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
- // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
- // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
- //
- using System.Threading;
- using System.Collections;
- using System.Collections.Generic;
- using System.Linq;
- using System.Security.Permissions;
- using System.Web.Configuration;
- namespace System.Web.Caching
- {
- // CAS - no InheritanceDemand here as the class is sealed
- [AspNetHostingPermission (SecurityAction.LinkDemand, Level = AspNetHostingPermissionLevel.Minimal)]
- public sealed class Cache: IEnumerable
- {
- const int LOW_WATER_MARK = 10000; // Target number of items if high water mark is reached
- const int HIGH_WATER_MARK = 15000; // We start collection after exceeding this count
-
- public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue;
- public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero;
- // cacheLock will be released in the code below without checking whether it was
- // actually acquired. The API doesn't offer a reliable way to check whether the lock
- // is being held by the current thread and since Mono does't implement CER
- // (Constrained Execution Regions -
- // http://msdn.microsoft.com/en-us/library/ms228973.aspx) currently, we have no
- // reliable way of recording the information that the lock has been successfully
- // acquired.
- // It can happen that a Thread.Abort occurs while acquiring the lock and the lock
- // isn't actually held. In this case the attempt to release a lock will throw an
- // exception. It's better than a race of setting a boolean flag after acquiring the
- // lock and then relying upon it here to release it - that may cause a deadlock
- // should we fail to release the lock which was successfully acquired but
- // Thread.Abort happened right after that during the stloc instruction to set the
- // boolean flag. Once CERs are supported we can use the boolean flag reliably.
- ReaderWriterLockSlim cacheLock;
- CacheItemLRU cache;
- CacheItemPriorityQueue timedItems;
- Timer expirationTimer;
- long expirationTimerPeriod = 0;
- Cache dependencyCache;
- bool? disableExpiration;
- long privateBytesLimit = -1;
- long percentagePhysicalMemoryLimit = -1;
-
- bool DisableExpiration {
- get {
- if (disableExpiration == null) {
- var cs = WebConfigurationManager.GetWebApplicationSection ("system.web/caching/cache") as CacheSection;
- if (cs == null)
- disableExpiration = false;
- else
- disableExpiration = (bool)cs.DisableExpiration;
- }
- return (bool)disableExpiration;
- }
- }
- public long EffectivePrivateBytesLimit {
- get {
- if (privateBytesLimit == -1) {
- var cs = WebConfigurationManager.GetWebApplicationSection ("system.web/caching/cache") as CacheSection;
- if (cs == null)
- privateBytesLimit = 0;
- else
- privateBytesLimit = cs.PrivateBytesLimit;
- if (privateBytesLimit == 0) {
- // http://blogs.msdn.com/tmarq/archive/2007/06/25/some-history-on-the-asp-net-cache-memory-limits.aspx
- // TODO: calculate
- privateBytesLimit = 734003200;
- }
- }
- return privateBytesLimit;
- }
- }
- public long EffectivePercentagePhysicalMemoryLimit {
- get {
- if (percentagePhysicalMemoryLimit == -1) {
- var cs = WebConfigurationManager.GetWebApplicationSection ("system.web/caching/cache") as CacheSection;
- if (cs == null)
- percentagePhysicalMemoryLimit = 0;
- else
- percentagePhysicalMemoryLimit = cs.PercentagePhysicalMemoryUsedLimit;
- if (percentagePhysicalMemoryLimit == 0) {
- // http://blogs.msdn.com/tmarq/archive/2007/06/25/some-history-on-the-asp-net-cache-memory-limits.aspx
- // TODO: calculate
- percentagePhysicalMemoryLimit = 97;
- }
- }
- return percentagePhysicalMemoryLimit;
- }
- }
-
- public Cache ()
- {
- cacheLock = new ReaderWriterLockSlim ();
- cache = new CacheItemLRU (this, HIGH_WATER_MARK, LOW_WATER_MARK);
- }
- public int Count {
- get { return cache.Count; }
- }
-
- public object this [string key] {
- get { return Get (key); }
- set { Insert (key, value); }
- }
- // Must ALWAYS be called with the cache write lock held
- CacheItem RemoveCacheItem (string key)
- {
- if (key == null)
- return null;
- CacheItem ret = cache [key];
- if (ret == null)
- return null;
-
- if (timedItems != null)
- timedItems.OnItemDisable (ret);
-
- ret.Disabled = true;
- cache.Remove (key);
-
- return ret;
- }
-
- public object Add (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
- {
- if (key == null)
- throw new ArgumentNullException ("key");
-
- try {
- cacheLock.EnterWriteLock ();
- CacheItem it = cache [key];
- if (it != null)
- return it.Value;
- Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback, null, false);
- } finally {
- // See comment at the top of the file, above cacheLock declaration
- cacheLock.ExitWriteLock ();
- }
-
- return null;
- }
-
- public object Get (string key)
- {
- try {
- cacheLock.EnterUpgradeableReadLock ();
- CacheItem it = cache [key];
- if (it == null)
- return null;
-
- if (it.Dependency != null && it.Dependency.HasChanged) {
- try {
- cacheLock.EnterWriteLock ();
- if (!NeedsUpdate (it, CacheItemUpdateReason.DependencyChanged, false))
- Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false, true);
- } finally {
- // See comment at the top of the file, above cacheLock declaration
- cacheLock.ExitWriteLock ();
- }
-
- return null;
- }
- if (!DisableExpiration) {
- if (it.SlidingExpiration != NoSlidingExpiration) {
- it.AbsoluteExpiration = DateTime.Now + it.SlidingExpiration;
- // Cast to long is ok since we know that sliding expiration
- // is less than 365 days (31536000000ms)
- long remaining = (long)it.SlidingExpiration.TotalMilliseconds;
- it.ExpiresAt = it.AbsoluteExpiration.Ticks;
-
- if (expirationTimer != null && (expirationTimerPeriod == 0 || expirationTimerPeriod > remaining)) {
- expirationTimerPeriod = remaining;
- expirationTimer.Change (expirationTimerPeriod, expirationTimerPeriod);
- }
-
- } else if (DateTime.Now >= it.AbsoluteExpiration) {
- try {
- cacheLock.EnterWriteLock ();
- if (!NeedsUpdate (it, CacheItemUpdateReason.Expired, false))
- Remove (key, CacheItemRemovedReason.Expired, false, true);
- } finally {
- // See comment at the top of the file, above cacheLock declaration
- cacheLock.ExitWriteLock ();
- }
- return null;
- }
- }
-
- return it.Value;
- } finally {
- // See comment at the top of the file, above cacheLock declaration
- cacheLock.ExitUpgradeableReadLock ();
- }
- }
-
- public void Insert (string key, object value)
- {
- Insert (key, value, null, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Normal, null, null, true);
- }
-
- public void Insert (string key, object value, CacheDependency dependencies)
- {
- Insert (key, value, dependencies, NoAbsoluteExpiration, NoSlidingExpiration, CacheItemPriority.Normal, null, null, true);
- }
-
- public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration)
- {
- Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, CacheItemPriority.Normal, null, null, true);
- }
- public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
- CacheItemUpdateCallback onUpdateCallback)
- {
- Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, CacheItemPriority.Normal, null, onUpdateCallback, true);
- }
-
- public void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
- CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback)
- {
- Insert (key, value, dependencies, absoluteExpiration, slidingExpiration, priority, onRemoveCallback, null, true);
- }
- void Insert (string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration,
- CacheItemPriority priority, CacheItemRemovedCallback onRemoveCallback, CacheItemUpdateCallback onUpdateCallback, bool doLock)
- {
- if (key == null)
- throw new ArgumentNullException ("key");
- if (value == null)
- throw new ArgumentNullException ("value");
- if (slidingExpiration < TimeSpan.Zero || slidingExpiration > TimeSpan.FromDays (365))
- throw new ArgumentNullException ("slidingExpiration");
- if (absoluteExpiration != NoAbsoluteExpiration && slidingExpiration != NoSlidingExpiration)
- throw new ArgumentException ("Both absoluteExpiration and slidingExpiration are specified");
-
- CacheItem ci = new CacheItem ();
- ci.Value = value;
- ci.Key = key;
-
- if (dependencies != null) {
- ci.Dependency = dependencies;
- dependencies.DependencyChanged += new EventHandler (OnDependencyChanged);
- dependencies.SetCache (DependencyCache);
- }
- ci.Priority = priority;
- SetItemTimeout (ci, absoluteExpiration, slidingExpiration, onRemoveCallback, onUpdateCallback, key, doLock);
- }
-
- internal void SetItemTimeout (string key, DateTime absoluteExpiration, TimeSpan slidingExpiration, bool doLock)
- {
- CacheItem ci = null;
- try {
- if (doLock)
- cacheLock.EnterWriteLock ();
-
- ci = cache [key];
- if (ci != null)
- SetItemTimeout (ci, absoluteExpiration, slidingExpiration, ci.OnRemoveCallback, null, key, false);
- } finally {
- if (doLock) {
- // See comment at the top of the file, above cacheLock declaration
- cacheLock.ExitWriteLock ();
- }
- }
- }
- void SetItemTimeout (CacheItem ci, DateTime absoluteExpiration, TimeSpan slidingExpiration, CacheItemRemovedCallback onRemoveCallback,
- CacheItemUpdateCallback onUpdateCallback, string key, bool doLock)
- {
- bool disableExpiration = DisableExpiration;
- if (!disableExpiration) {
- ci.SlidingExpiration = slidingExpiration;
- if (slidingExpiration != NoSlidingExpiration)
- ci.AbsoluteExpiration = DateTime.Now + slidingExpiration;
- else
- ci.AbsoluteExpiration = absoluteExpiration;
- }
-
- ci.OnRemoveCallback = onRemoveCallback;
- ci.OnUpdateCallback = onUpdateCallback;
-
- try {
- if (doLock)
- cacheLock.EnterWriteLock ();
- if (key != null) {
- cache [key] = ci;
- cache.EvictIfNecessary ();
- }
-
- ci.LastChange = DateTime.Now;
- if (!disableExpiration && ci.AbsoluteExpiration != NoAbsoluteExpiration) {
- bool enqueue;
- if (ci.IsTimedItem) {
- enqueue = UpdateTimedItem (ci);
- if (!enqueue)
- UpdateTimerPeriod (ci);
- } else
- enqueue = true;
- if (enqueue) {
- ci.IsTimedItem = true;
- EnqueueTimedItem (ci);
- }
-
- }
- } finally {
- if (doLock) {
- // See comment at the top of the file, above cacheLock declaration
- cacheLock.ExitWriteLock ();
- }
- }
- }
- // MUST be called with cache lock held
- bool UpdateTimedItem (CacheItem item)
- {
- if (timedItems == null)
- return true;
- item.ExpiresAt = item.AbsoluteExpiration.Ticks;
- return !timedItems.Update (item);
- }
- // MUST be called with cache lock held
- void UpdateTimerPeriod (CacheItem item)
- {
- if (timedItems == null)
- timedItems = new CacheItemPriorityQueue ();
- long remaining = Math.Max (0, (long)(item.AbsoluteExpiration - DateTime.Now).TotalMilliseconds);
- item.ExpiresAt = item.AbsoluteExpiration.Ticks;
-
- if (remaining > 4294967294)
- // Maximum due time for timer
- // Item will expire properly anyway, as the timer will be
- // rescheduled for the item's expiration time once that item is
- // bubbled to the top of the priority queue.
- remaining = 4294967294;
- if (expirationTimer != null && expirationTimerPeriod <= remaining)
- return;
- expirationTimerPeriod = remaining;
- if (expirationTimer == null)
- expirationTimer = new Timer (new TimerCallback (ExpireItems), null, expirationTimerPeriod, expirationTimerPeriod);
- else
- expirationTimer.Change (expirationTimerPeriod, expirationTimerPeriod);
- }
-
- // MUST be called with cache lock held
- void EnqueueTimedItem (CacheItem item)
- {
- UpdateTimerPeriod (item);
- timedItems.Enqueue (item);
- }
- public object Remove (string key)
- {
- return Remove (key, CacheItemRemovedReason.Removed, true, true);
- }
-
- internal object Remove (string key, CacheItemRemovedReason reason, bool doLock, bool invokeCallback)
- {
- CacheItem it = null;
- try {
- if (doLock)
- cacheLock.EnterWriteLock ();
-
- it = RemoveCacheItem (key);
- } finally {
- if (doLock) {
- // See comment at the top of the file, above cacheLock declaration
- cacheLock.ExitWriteLock ();
- }
- }
- object ret = null;
- if (it != null) {
- if (it.Dependency != null) {
- it.Dependency.SetCache (null);
- it.Dependency.DependencyChanged -= new EventHandler (OnDependencyChanged);
- it.Dependency.Dispose ();
- }
- if (invokeCallback && it.OnRemoveCallback != null) {
- try {
- it.OnRemoveCallback (key, it.Value, reason);
- } catch {
- //TODO: anything to be done here?
- }
- }
- ret = it.Value;
- it.Value = null;
- it.Key = null;
- it.Dependency = null;
- it.OnRemoveCallback = null;
- it.OnUpdateCallback = null;
- it = null;
- }
- return ret;
- }
- // Used when shutting down the application so that
- // session_end events are sent for all sessions.
- internal void InvokePrivateCallbacks ()
- {
- try {
- cacheLock.EnterReadLock ();
- cache.InvokePrivateCallbacks ();
- } finally {
- // See comment at the top of the file, above cacheLock declaration
- cacheLock.ExitReadLock ();
- }
- }
- public IDictionaryEnumerator GetEnumerator ()
- {
- List <CacheItem> list = null;
- try {
- cacheLock.EnterReadLock ();
- list = cache.ToList ();
- } finally {
- // See comment at the top of the file, above cacheLock declaration
- cacheLock.ExitReadLock ();
- }
-
- return new CacheItemEnumerator (list);
- }
-
- IEnumerator IEnumerable.GetEnumerator ()
- {
- return GetEnumerator ();
- }
-
- void OnDependencyChanged (object o, EventArgs a)
- {
- CheckDependencies ();
- }
- bool NeedsUpdate (CacheItem item, CacheItemUpdateReason reason, bool needLock)
- {
- try {
- if (needLock)
- cacheLock.EnterWriteLock ();
-
- if (item == null || item.OnUpdateCallback == null)
- return false;
- object expensiveObject;
- CacheDependency dependency;
- DateTime absoluteExpiration;
- TimeSpan slidingExpiration;
- string key = item.Key;
- CacheItemUpdateCallback updateCB = item.OnUpdateCallback;
-
- updateCB (key, reason, out expensiveObject, out dependency, out absoluteExpiration, out slidingExpiration);
- if (expensiveObject == null)
- return false;
- CacheItemPriority priority = item.Priority;
- CacheItemRemovedCallback removeCB = item.OnRemoveCallback;
- CacheItemRemovedReason whyRemoved;
- switch (reason) {
- case CacheItemUpdateReason.Expired:
- whyRemoved = CacheItemRemovedReason.Expired;
- break;
- case CacheItemUpdateReason.DependencyChanged:
- whyRemoved = CacheItemRemovedReason.DependencyChanged;
- break;
- default:
- whyRemoved = CacheItemRemovedReason.Removed;
- break;
- }
-
- Remove (key, whyRemoved, false, false);
- Insert (key, expensiveObject, dependency, absoluteExpiration, slidingExpiration, priority, removeCB, updateCB, false);
-
- return true;
- } catch (Exception) {
- return false;
- } finally {
- if (needLock) {
- // See comment at the top of the file, above cacheLock declaration
- cacheLock.ExitWriteLock ();
- }
- }
- }
-
- void ExpireItems (object data)
- {
- DateTime now = DateTime.Now;
- CacheItem item = null;
- expirationTimer.Change (Timeout.Infinite, Timeout.Infinite);
- try {
- cacheLock.EnterWriteLock ();
- while (true) {
- item = timedItems.Peek ();
-
- if (item == null) {
- if (timedItems.Count == 0)
- break;
-
- timedItems.Dequeue ();
- continue;
- }
-
- if (!item.Disabled && item.ExpiresAt > now.Ticks)
- break;
-
- if (item.Disabled) {
- item = timedItems.Dequeue ();
- continue;
- }
- item = timedItems.Dequeue ();
- if (item != null)
- if (!NeedsUpdate (item, CacheItemUpdateReason.Expired, false))
- Remove (item.Key, CacheItemRemovedReason.Expired, false, true);
- }
- } finally {
- // See comment at the top of the file, above cacheLock declaration
- cacheLock.ExitWriteLock ();
- }
- if (item != null) {
- long remaining = Math.Max (0, (long)(item.AbsoluteExpiration - now).TotalMilliseconds);
- if (remaining > 0 && (expirationTimerPeriod == 0 || expirationTimerPeriod > remaining)) {
- expirationTimerPeriod = remaining;
- expirationTimer.Change (expirationTimerPeriod, expirationTimerPeriod);
- return;
- }
- if (expirationTimerPeriod > 0)
- return;
- }
- expirationTimer.Change (Timeout.Infinite, Timeout.Infinite);
- expirationTimerPeriod = 0;
- }
-
- internal void CheckDependencies ()
- {
- try {
- cacheLock.EnterWriteLock ();
- List <CacheItem> list = cache.SelectItems (it => {
- if (it == null)
- return false;
- if (it.Dependency != null && it.Dependency.HasChanged && !NeedsUpdate (it, CacheItemUpdateReason.DependencyChanged, false))
- return true;
- return false;
- });
-
- foreach (CacheItem it in list)
- Remove (it.Key, CacheItemRemovedReason.DependencyChanged, false, true);
- list.Clear ();
- list.TrimExcess ();
- } finally {
- // See comment at the top of the file, above cacheLock declaration
- cacheLock.ExitWriteLock ();
- }
- }
-
- internal DateTime GetKeyLastChange (string key)
- {
- try {
- cacheLock.EnterReadLock ();
- CacheItem it = cache [key];
- if (it == null)
- return DateTime.MaxValue;
-
- return it.LastChange;
- } finally {
- // See comment at the top of the file, above cacheLock declaration
- cacheLock.ExitReadLock ();
- }
- }
- internal Cache DependencyCache {
- get {
- if (dependencyCache == null)
- return this;
- return dependencyCache;
- }
- set { dependencyCache = value; }
- }
- }
- }