e46a49ecf1
Former-commit-id: d0813289fa2d35e1f8ed77530acb4fb1df441bc0
536 lines
23 KiB
C#
536 lines
23 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="cache.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
/*
|
|
* Cache class
|
|
*
|
|
* Copyright (c) 1999 Microsoft Corporation
|
|
*/
|
|
|
|
namespace System.Web.Caching {
|
|
using System.Collections;
|
|
using System.Collections.Specialized;
|
|
using System.Configuration;
|
|
using System.Configuration.Provider;
|
|
using System.Diagnostics;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Reflection;
|
|
using System.Runtime.InteropServices;
|
|
using System.Threading;
|
|
using System.Web.Util;
|
|
using System.Web;
|
|
using Microsoft.Win32;
|
|
using System.Security.Permissions;
|
|
using System.Globalization;
|
|
using System.Web.Configuration;
|
|
using System.Web.Hosting;
|
|
using System.Web.Management;
|
|
using Debug = System.Web.Util.Debug;
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Represents the method that will handle the <see langword='onRemoveCallback'/>
|
|
/// event of a System.Web.Caching.Cache instance.</para>
|
|
/// </devdoc>
|
|
public delegate void CacheItemRemovedCallback(
|
|
string key, object value, CacheItemRemovedReason reason);
|
|
|
|
/// <devdoc>
|
|
/// <para>Represents the method that will handle the <see langword='onUpdateCallback'/>
|
|
/// event of a System.Web.Caching.Cache instance.</para>
|
|
/// </devdoc>
|
|
[SuppressMessage("Microsoft.Design", "CA1021:AvoidOutParameters",
|
|
Justification="Shipped this way in NetFx 2.0 SP2")]
|
|
public delegate void CacheItemUpdateCallback(
|
|
string key, CacheItemUpdateReason reason,
|
|
out object expensiveObject, out CacheDependency dependency, out DateTime absoluteExpiration, out TimeSpan slidingExpiration);
|
|
|
|
/// <devdoc>
|
|
/// <para> Specifies the relative priority of items stored in the System.Web.Caching.Cache. When the Web
|
|
/// server runs low on memory, the Cache selectively purges items to free system
|
|
/// memory. Items with higher priorities are less likely to be removed from the
|
|
/// cache when the server is under load. Web
|
|
/// applications can use these
|
|
/// values to prioritize cached items relative to one another. The default is
|
|
/// normal.</para>
|
|
/// </devdoc>
|
|
public enum CacheItemPriority {
|
|
|
|
/// <devdoc>
|
|
/// <para> The cahce items with this priority level will be the first
|
|
/// to be removed when the server frees system memory by deleting items from the
|
|
/// cache.</para>
|
|
/// </devdoc>
|
|
Low = 1,
|
|
|
|
/// <devdoc>
|
|
/// <para> The cache items with this priority level
|
|
/// are in the second group to be removed when the server frees system memory by
|
|
/// deleting items from the cache. </para>
|
|
/// </devdoc>
|
|
BelowNormal,
|
|
|
|
/// <devdoc>
|
|
/// <para> The cache items with this priority level are in
|
|
/// the third group to be removed when the server frees system memory by deleting items from the cache. This is the default. </para>
|
|
/// </devdoc>
|
|
Normal,
|
|
|
|
/// <devdoc>
|
|
/// <para> The cache items with this priority level are in the
|
|
/// fourth group to be removed when the server frees system memory by deleting items from the
|
|
/// cache. </para>
|
|
/// </devdoc>
|
|
AboveNormal,
|
|
|
|
/// <devdoc>
|
|
/// <para>The cache items with this priority level are in the fifth group to be removed
|
|
/// when the server frees system memory by deleting items from the cache. </para>
|
|
/// </devdoc>
|
|
High,
|
|
|
|
/// <devdoc>
|
|
/// <para>The cache items with this priority level will not be removed when the server
|
|
/// frees system memory by deleting items from the cache. </para>
|
|
/// </devdoc>
|
|
NotRemovable,
|
|
|
|
/// <devdoc>
|
|
/// <para>The default value is Normal.</para>
|
|
/// </devdoc>
|
|
Default = Normal
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Specifies the reason that a cached item was removed.</para>
|
|
/// </devdoc>
|
|
public enum CacheItemRemovedReason {
|
|
|
|
/// <devdoc>
|
|
/// <para>The item was removed from the cache by the 'System.Web.Caching.Cache.Remove' method, or by an System.Web.Caching.Cache.Insert method call specifying the same key.</para>
|
|
/// </devdoc>
|
|
Removed = 1,
|
|
|
|
/// <devdoc>
|
|
/// <para>The item was removed from the cache because it expired. </para>
|
|
/// </devdoc>
|
|
Expired,
|
|
|
|
/// <devdoc>
|
|
/// <para>The item was removed from the cache because the value in the hitInterval
|
|
/// parameter was not met, or because the system removed it to free memory.</para>
|
|
/// </devdoc>
|
|
Underused,
|
|
|
|
/// <devdoc>
|
|
/// <para>The item was removed from the cache because a file or key dependency was
|
|
/// changed.</para>
|
|
/// </devdoc>
|
|
DependencyChanged
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// <para>Specifies the reason why a cached item needs to be updated.</para>
|
|
/// </devdoc>
|
|
[SuppressMessage("Microsoft.Design", "CA1008:EnumsShouldHaveZeroValue",
|
|
Justification = "This enum should mirror CacheItemRemovedReason enum in design")]
|
|
public enum CacheItemUpdateReason {
|
|
|
|
/// <devdoc>
|
|
/// <para>The item needs to be updated because it expired. </para>
|
|
/// </devdoc>
|
|
Expired = 1,
|
|
|
|
/// <devdoc>
|
|
/// <para>The item needs to be updated because a file or key dependency was
|
|
/// changed.</para>
|
|
/// </devdoc>
|
|
DependencyChanged
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// <para>Implements the cache for a Web application. There is only one instance of
|
|
/// this class per application domain, and it remains valid only as long as the
|
|
/// application domain remains active. Information about an instance of this class
|
|
/// is available through the <see langword='Cache'/> property of the System.Web.HttpContext.</para>
|
|
/// </devdoc>
|
|
|
|
//
|
|
// Extra notes:
|
|
// - The Cache object contains a ICacheStore object and wraps it for public consumption.
|
|
//
|
|
public sealed class Cache : IEnumerable {
|
|
|
|
/// <devdoc>
|
|
/// <para>Sets the absolute expiration policy to, in essence,
|
|
/// never. When set, this field is equal to the the System.DateTime.MaxValue , which is a constant
|
|
/// representing the largest possible <see langword='DateTime'/> value. The maximum date and
|
|
/// time value is equivilant to "12/31/9999 11:59:59 PM". This field is read-only.</para>
|
|
/// </devdoc>
|
|
public static readonly DateTime NoAbsoluteExpiration = DateTime.MaxValue;
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Sets the amount of time for sliding cache expirations to
|
|
/// zero. When set, this field is equal to the System.TimeSpan.Zero field, which is a constant value of
|
|
/// zero. This field is read-only.</para>
|
|
/// </devdoc>
|
|
public static readonly TimeSpan NoSlidingExpiration = TimeSpan.Zero;
|
|
|
|
static CacheStoreProvider _objectCache = null;
|
|
static CacheStoreProvider _internalCache = null;
|
|
static CacheItemRemovedCallback s_sentinelRemovedCallback = new CacheItemRemovedCallback(SentinelEntry.OnCacheItemRemovedCallback);
|
|
|
|
/// <internalonly/>
|
|
/// <devdoc>
|
|
/// <para>This constructor is for internal use only, and was accidentally made public - do not use.</para>
|
|
/// </devdoc>
|
|
[SecurityPermission(SecurityAction.Demand, Unrestricted=true)]
|
|
public Cache() {
|
|
}
|
|
|
|
//
|
|
// internal ctor used by CacheCommon that avoids the demand for UnmanagedCode.
|
|
//
|
|
internal Cache(int dummy) {
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Gets the number of items stored in the cache. This value can be useful when
|
|
/// monitoring your application's performance or when using the ASP.NET tracing
|
|
/// functionality.</para>
|
|
/// </devdoc>
|
|
public int Count {
|
|
get {
|
|
return Convert.ToInt32(ObjectCache.ItemCount);
|
|
}
|
|
}
|
|
|
|
|
|
internal CacheStoreProvider GetInternalCache(bool createIfDoesNotExist) {
|
|
if (_internalCache == null && createIfDoesNotExist) {
|
|
lock (this) {
|
|
if (_internalCache == null) {
|
|
NameValueCollection cacheProviderSettings = HostingEnvironment.CacheStoreProviderSettings;
|
|
|
|
if (cacheProviderSettings != null) {
|
|
string providerName = (string)cacheProviderSettings["name"]; // Grab this now, as InstantiateProvider will remove it from settings
|
|
cacheProviderSettings["isPublic"] = "false";
|
|
_internalCache = (CacheStoreProvider)ProvidersHelper.InstantiateProvider(cacheProviderSettings, typeof(CacheStoreProvider));
|
|
_internalCache.Initialize(providerName, cacheProviderSettings);
|
|
}
|
|
else {
|
|
if (_objectCache is AspNetCache) {
|
|
_internalCache = new AspNetCache((AspNetCache)_objectCache, isPublic: false);
|
|
}
|
|
else {
|
|
_internalCache = new AspNetCache(isPublic: false);
|
|
}
|
|
_internalCache.Initialize(null, new NameValueCollection());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return _internalCache;
|
|
}
|
|
|
|
[PermissionSet(SecurityAction.Assert, Unrestricted = true)]
|
|
[SuppressMessage("Microsoft.Security", "CA2122:DoNotIndirectlyExposeMethodsWithLinkDemands", Justification = "We carefully control this method's callers.")]
|
|
internal CacheStoreProvider GetObjectCache(bool createIfDoesNotExist) {
|
|
if (_objectCache == null && createIfDoesNotExist) {
|
|
lock (this) {
|
|
if (_objectCache == null) {
|
|
NameValueCollection cacheProviderSettings = HostingEnvironment.CacheStoreProviderSettings;
|
|
|
|
if (cacheProviderSettings != null) {
|
|
string providerName = (string)cacheProviderSettings["name"]; // Grab this now, as InstantiateProvider will remove it from settings
|
|
cacheProviderSettings["isPublic"] = "true";
|
|
_objectCache = (CacheStoreProvider)ProvidersHelper.InstantiateProvider(cacheProviderSettings, typeof(CacheStoreProvider));
|
|
_objectCache.Initialize(providerName, cacheProviderSettings);
|
|
}
|
|
else {
|
|
if (_internalCache is AspNetCache) {
|
|
_objectCache = new AspNetCache((AspNetCache)_internalCache, isPublic: true);
|
|
}
|
|
else {
|
|
_objectCache = new AspNetCache(isPublic: true);
|
|
}
|
|
_objectCache.Initialize(null, new NameValueCollection());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return _objectCache;
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// <para>Provides access to the cache store used by ASP.Net internals.</para>
|
|
/// </devdoc>
|
|
internal CacheStoreProvider InternalCache {
|
|
get { return GetInternalCache(createIfDoesNotExist: true); }
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// <para>Provides access to the cache store that backs HttpRuntime.Cache.</para>
|
|
/// </devdoc>
|
|
internal CacheStoreProvider ObjectCache {
|
|
get { return GetObjectCache(createIfDoesNotExist: true); }
|
|
}
|
|
|
|
/// <internalonly/>
|
|
IEnumerator IEnumerable.GetEnumerator() {
|
|
return ObjectCache.GetEnumerator();
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Returns a dictionary enumerator used for iterating through the key/value
|
|
/// pairs contained in the cache. Items can be added to or removed from the cache
|
|
/// while this method is enumerating through the cache items.</para>
|
|
/// </devdoc>
|
|
public IDictionaryEnumerator GetEnumerator() {
|
|
return ObjectCache.GetEnumerator();
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Gets or sets an item in the cache.</para>
|
|
/// </devdoc>
|
|
public object this[string key] {
|
|
get {
|
|
return Get(key);
|
|
}
|
|
|
|
set {
|
|
Insert(key, value);
|
|
}
|
|
}
|
|
|
|
private class SentinelEntry {
|
|
private string _key;
|
|
private CacheDependency _expensiveObjectDependency;
|
|
private CacheItemUpdateCallback _cacheItemUpdateCallback;
|
|
|
|
public SentinelEntry(string key, CacheDependency expensiveObjectDependency, CacheItemUpdateCallback callback) {
|
|
_key = key;
|
|
_expensiveObjectDependency = expensiveObjectDependency;
|
|
_cacheItemUpdateCallback = callback;
|
|
}
|
|
|
|
public string Key {
|
|
get { return _key; }
|
|
}
|
|
|
|
public CacheDependency ExpensiveObjectDependency {
|
|
get { return _expensiveObjectDependency; }
|
|
}
|
|
|
|
public CacheItemUpdateCallback CacheItemUpdateCallback {
|
|
get { return _cacheItemUpdateCallback; }
|
|
}
|
|
|
|
public static void OnCacheItemRemovedCallback(string key, object value, CacheItemRemovedReason reason) {
|
|
CacheItemUpdateReason updateReason;
|
|
SentinelEntry entry = value as SentinelEntry;
|
|
|
|
switch (reason) {
|
|
case CacheItemRemovedReason.Expired:
|
|
updateReason = CacheItemUpdateReason.Expired;
|
|
break;
|
|
case CacheItemRemovedReason.DependencyChanged:
|
|
updateReason = CacheItemUpdateReason.DependencyChanged;
|
|
if (entry.ExpensiveObjectDependency.HasChanged) {
|
|
// If the expensiveObject has been removed explicitly by Cache.Remove,
|
|
// return from the SentinelEntry removed callback
|
|
// thus effectively removing the SentinelEntry from the cache.
|
|
return;
|
|
}
|
|
break;
|
|
case CacheItemRemovedReason.Underused:
|
|
Debug.Fail("Reason should never be CacheItemRemovedReason.Underused since the entry was inserted as NotRemovable.");
|
|
return;
|
|
default:
|
|
// do nothing if reason is Removed
|
|
return;
|
|
}
|
|
|
|
CacheDependency cacheDependency;
|
|
DateTime absoluteExpiration;
|
|
TimeSpan slidingExpiration;
|
|
object expensiveObject;
|
|
CacheItemUpdateCallback callback = entry.CacheItemUpdateCallback;
|
|
// invoke update callback
|
|
try {
|
|
callback(entry.Key, updateReason, out expensiveObject, out cacheDependency, out absoluteExpiration, out slidingExpiration);
|
|
// Dev10 861163 - Only update the "expensive" object if the user returns a new object and the
|
|
// cache dependency hasn't changed. (Inserting with a cache dependency that has already changed will cause recursion.)
|
|
if (expensiveObject != null && (cacheDependency == null || !cacheDependency.HasChanged)) {
|
|
HttpRuntime.Cache.Insert(entry.Key, expensiveObject, cacheDependency, absoluteExpiration, slidingExpiration, entry.CacheItemUpdateCallback);
|
|
}
|
|
else {
|
|
HttpRuntime.Cache.Remove(entry.Key);
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
HttpRuntime.Cache.Remove(entry.Key);
|
|
try {
|
|
WebBaseEvent.RaiseRuntimeError(e, value);
|
|
}
|
|
catch {
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <devdoc>
|
|
/// <para>Retrieves an item from the cache.</para>
|
|
/// </devdoc>
|
|
public object Get(string key) {
|
|
return ObjectCache.Get(key);
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Inserts an item into the Cache with default values.</para>
|
|
/// </devdoc>
|
|
public void Insert(string key, object value) {
|
|
ObjectCache.Insert(key, value, options: null);
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Inserts an object into the System.Web.Caching.Cache that has file or key
|
|
/// dependencies.</para>
|
|
/// </devdoc>
|
|
public void Insert(string key, object value, CacheDependency dependencies) {
|
|
ObjectCache.Insert(key, value, new CacheInsertOptions() { Dependencies = dependencies });
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Inserts an object into the System.Web.Caching.Cache that has file or key dependencies and
|
|
/// expires at the value set in the <paramref name="absoluteExpiration"/> parameter.</para>
|
|
/// </devdoc>
|
|
public void Insert(string key, object value, CacheDependency dependencies, DateTime absoluteExpiration, TimeSpan slidingExpiration) {
|
|
DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
|
|
ObjectCache.Insert(key, value, new CacheInsertOptions() {
|
|
Dependencies = dependencies,
|
|
AbsoluteExpiration = utcAbsoluteExpiration,
|
|
SlidingExpiration = slidingExpiration
|
|
});
|
|
}
|
|
|
|
public void Insert(
|
|
string key,
|
|
object value,
|
|
CacheDependency dependencies,
|
|
DateTime absoluteExpiration,
|
|
TimeSpan slidingExpiration,
|
|
CacheItemPriority priority,
|
|
CacheItemRemovedCallback onRemoveCallback) {
|
|
|
|
DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
|
|
ObjectCache.Insert(key, value, new CacheInsertOptions() {
|
|
Dependencies = dependencies,
|
|
AbsoluteExpiration = utcAbsoluteExpiration,
|
|
SlidingExpiration = slidingExpiration,
|
|
Priority = priority,
|
|
OnRemovedCallback = onRemoveCallback
|
|
});
|
|
}
|
|
|
|
// DevDiv Bugs 162763:
|
|
// Add a an event that fires *before* an item is evicted from the ASP.NET Cache
|
|
public void Insert(
|
|
string key,
|
|
object value,
|
|
CacheDependency dependencies,
|
|
DateTime absoluteExpiration,
|
|
TimeSpan slidingExpiration,
|
|
CacheItemUpdateCallback onUpdateCallback) {
|
|
|
|
if (dependencies == null && absoluteExpiration == Cache.NoAbsoluteExpiration && slidingExpiration == Cache.NoSlidingExpiration) {
|
|
throw new ArgumentException(SR.GetString(SR.Invalid_Parameters_To_Insert));
|
|
}
|
|
if (onUpdateCallback == null) {
|
|
throw new ArgumentNullException("onUpdateCallback");
|
|
}
|
|
DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
|
|
// Insert updatable cache entry
|
|
ObjectCache.Insert(key, value, new CacheInsertOptions() { Priority = CacheItemPriority.NotRemovable });
|
|
|
|
// Ensure the sentinel depends on its updatable entry
|
|
string[] cacheKeys = { key };
|
|
CacheDependency expensiveObjectDep = new CacheDependency(null, cacheKeys);
|
|
if (dependencies == null) {
|
|
dependencies = expensiveObjectDep;
|
|
}
|
|
else {
|
|
AggregateCacheDependency deps = new AggregateCacheDependency();
|
|
deps.Add(dependencies, expensiveObjectDep);
|
|
dependencies = deps;
|
|
}
|
|
// Insert sentinel entry for the updatable cache entry
|
|
HttpRuntime.Cache.InternalCache.Insert(
|
|
CacheInternal.PrefixValidationSentinel + key,
|
|
new SentinelEntry(key, expensiveObjectDep, onUpdateCallback),
|
|
new CacheInsertOptions() {
|
|
Dependencies = dependencies,
|
|
AbsoluteExpiration = utcAbsoluteExpiration,
|
|
SlidingExpiration = slidingExpiration,
|
|
Priority = CacheItemPriority.NotRemovable,
|
|
OnRemovedCallback = Cache.s_sentinelRemovedCallback
|
|
});
|
|
}
|
|
|
|
|
|
public object Add(
|
|
string key,
|
|
object value,
|
|
CacheDependency dependencies,
|
|
DateTime absoluteExpiration,
|
|
TimeSpan slidingExpiration,
|
|
CacheItemPriority priority,
|
|
CacheItemRemovedCallback onRemoveCallback) {
|
|
|
|
DateTime utcAbsoluteExpiration = DateTimeUtil.ConvertToUniversalTime(absoluteExpiration);
|
|
return ObjectCache.Add(key, value, new CacheInsertOptions() {
|
|
Dependencies = dependencies,
|
|
AbsoluteExpiration = utcAbsoluteExpiration,
|
|
SlidingExpiration = slidingExpiration,
|
|
Priority = priority,
|
|
OnRemovedCallback = onRemoveCallback
|
|
});
|
|
}
|
|
|
|
|
|
/// <devdoc>
|
|
/// <para>Removes the specified item from the cache. </para>
|
|
/// </devdoc>
|
|
public object Remove(string key) {
|
|
return ObjectCache.Remove(key, CacheItemRemovedReason.Removed);
|
|
}
|
|
|
|
public long EffectivePrivateBytesLimit {
|
|
get {
|
|
return AspNetMemoryMonitor.ProcessPrivateBytesLimit;
|
|
}
|
|
}
|
|
|
|
public long EffectivePercentagePhysicalMemoryLimit {
|
|
get {
|
|
return AspNetMemoryMonitor.PhysicalMemoryPercentageLimit;
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|