536cd135cc
Former-commit-id: 5624ac747d633e885131e8349322922b6a59baaa
434 lines
15 KiB
C#
434 lines
15 KiB
C#
//------------------------------------------------------------------------------
|
|
// <copyright file="CacheEntry.cs" company="Microsoft">
|
|
// Copyright (c) Microsoft Corporation. All rights reserved.
|
|
// </copyright>
|
|
//------------------------------------------------------------------------------
|
|
|
|
/*
|
|
* CacheEntry
|
|
*
|
|
* Copyright (c) 1998-1999, Microsoft Corporation
|
|
*
|
|
*/
|
|
|
|
namespace System.Web.Caching {
|
|
using System.Text;
|
|
using System.Threading;
|
|
using System.Web;
|
|
using System.Web.Util;
|
|
using System.Collections;
|
|
using System.Web.Management;
|
|
using System.Web.Hosting;
|
|
using System.Globalization;
|
|
|
|
internal class CacheKey {
|
|
protected const byte BitPublic = 0x20;
|
|
protected const byte BitOutputCache = 0x40;
|
|
|
|
protected string _key; /* key to the item */
|
|
protected byte _bits; /* cache lifetime state and public property */
|
|
int _hashCode;
|
|
|
|
internal CacheKey(String key, bool isPublic) {
|
|
if (key == null) {
|
|
throw new ArgumentNullException("key");
|
|
}
|
|
|
|
_key = key;
|
|
if (isPublic) {
|
|
_bits = BitPublic;
|
|
}
|
|
else if (key[0] == CacheInternal.PrefixOutputCache[0]) {
|
|
_bits |= BitOutputCache;
|
|
}
|
|
|
|
#if DBG
|
|
if (!isPublic) {
|
|
Debug.Assert(CacheInternal.PrefixFIRST[0] <= key[0] && key[0] <= CacheInternal.PrefixLAST[0],
|
|
"CacheInternal.PrefixFIRST[0] <= key[0] && key[0] <= CacheInternal.PrefixLAST[0], key=" + key);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
internal String Key {
|
|
get { return _key; }
|
|
}
|
|
|
|
internal bool IsOutputCache {
|
|
get { return (_bits & BitOutputCache) != 0; }
|
|
}
|
|
|
|
internal bool IsPublic {
|
|
get { return (_bits & BitPublic) != 0; }
|
|
}
|
|
|
|
public override int GetHashCode() {
|
|
if (_hashCode == 0) {
|
|
_hashCode = _key.GetHashCode();
|
|
}
|
|
|
|
return _hashCode;
|
|
}
|
|
|
|
#if DBG
|
|
public override string ToString() {
|
|
return (IsPublic ? "P:" : "I:") + _key;
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* An entry in the cache.
|
|
* Overhead is 68 bytes + object header.
|
|
*/
|
|
internal sealed class CacheEntry : CacheKey {
|
|
const CacheItemPriority CacheItemPriorityMin = CacheItemPriority.Low;
|
|
const CacheItemPriority CacheItemPriorityMax = CacheItemPriority.NotRemovable;
|
|
static readonly TimeSpan OneYear = new TimeSpan(365, 0, 0, 0);
|
|
|
|
|
|
internal enum EntryState : byte {
|
|
NotInCache = 0x00, // Created but not in hashtable
|
|
AddingToCache = 0x01, // In hashtable only
|
|
AddedToCache = 0x02, // In hashtable + expires + usage
|
|
RemovingFromCache = 0x04, // Removed from hashtable only
|
|
RemovedFromCache = 0x08, // Removed from hashtable & expires & usage
|
|
Closed = 0x10,
|
|
}
|
|
|
|
const byte EntryStateMask = 0x1f;
|
|
// protected const byte BitPublic = 0x20;
|
|
|
|
// item
|
|
object _value; /* value */
|
|
DateTime _utcCreated; /* creation date */
|
|
|
|
|
|
// expiration
|
|
DateTime _utcExpires; /* when this item expires */
|
|
TimeSpan _slidingExpiration; /* expiration interval */
|
|
byte _expiresBucket; /* index of the expiration list (bucket) */
|
|
ExpiresEntryRef _expiresEntryRef; /* ref into the expiration list */
|
|
|
|
// usage
|
|
byte _usageBucket; /* index of the usage list (== priority-1) */
|
|
UsageEntryRef _usageEntryRef; /* ref into the usage list */
|
|
DateTime _utcLastUpdate; /* time we last updated usage */
|
|
CacheInternal _cache;
|
|
|
|
// dependencies
|
|
CacheDependency _dependency; /* dependencies this item has */
|
|
object _onRemovedTargets; /* targets of OnRemove notification */
|
|
|
|
/*
|
|
* ctor.
|
|
*/
|
|
|
|
internal CacheEntry(
|
|
String key,
|
|
Object value,
|
|
CacheDependency dependency,
|
|
CacheItemRemovedCallback onRemovedHandler,
|
|
DateTime utcAbsoluteExpiration,
|
|
TimeSpan slidingExpiration,
|
|
CacheItemPriority priority,
|
|
bool isPublic,
|
|
CacheInternal cache) :
|
|
|
|
base(key, isPublic) {
|
|
|
|
if (value == null) {
|
|
throw new ArgumentNullException("value");
|
|
}
|
|
|
|
if (slidingExpiration < TimeSpan.Zero || OneYear < slidingExpiration) {
|
|
throw new ArgumentOutOfRangeException("slidingExpiration");
|
|
}
|
|
|
|
if (utcAbsoluteExpiration != Cache.NoAbsoluteExpiration && slidingExpiration != Cache.NoSlidingExpiration) {
|
|
throw new ArgumentException(SR.GetString(SR.Invalid_expiration_combination));
|
|
}
|
|
|
|
if (priority < CacheItemPriorityMin || CacheItemPriorityMax < priority) {
|
|
throw new ArgumentOutOfRangeException("priority");
|
|
}
|
|
|
|
_value = value;
|
|
_dependency = dependency;
|
|
_onRemovedTargets = onRemovedHandler;
|
|
|
|
_utcCreated = DateTime.UtcNow;
|
|
_slidingExpiration = slidingExpiration;
|
|
if (_slidingExpiration > TimeSpan.Zero) {
|
|
_utcExpires = _utcCreated + _slidingExpiration;
|
|
}
|
|
else {
|
|
_utcExpires = utcAbsoluteExpiration;
|
|
}
|
|
|
|
_expiresEntryRef = ExpiresEntryRef.INVALID;
|
|
_expiresBucket = 0xff;
|
|
|
|
_usageEntryRef = UsageEntryRef.INVALID;
|
|
if (priority == CacheItemPriority.NotRemovable) {
|
|
_usageBucket = 0xff;
|
|
}
|
|
else {
|
|
_usageBucket = (byte) (priority - 1);
|
|
}
|
|
|
|
_cache = cache;
|
|
}
|
|
|
|
internal Object Value {
|
|
get {return _value;}
|
|
}
|
|
|
|
internal DateTime UtcCreated {
|
|
get {return _utcCreated;}
|
|
}
|
|
|
|
internal EntryState State {
|
|
get { return (EntryState) (_bits & EntryStateMask); }
|
|
set { _bits = (byte) (((uint) _bits & ~(uint)EntryStateMask) | (uint) value); }
|
|
}
|
|
|
|
internal DateTime UtcExpires {
|
|
get {return _utcExpires;}
|
|
set {_utcExpires = value;}
|
|
}
|
|
|
|
internal TimeSpan SlidingExpiration {
|
|
get {return _slidingExpiration;}
|
|
}
|
|
|
|
internal byte ExpiresBucket {
|
|
get {return _expiresBucket;}
|
|
set {_expiresBucket = value;}
|
|
}
|
|
|
|
internal ExpiresEntryRef ExpiresEntryRef {
|
|
get {return _expiresEntryRef;}
|
|
set {_expiresEntryRef = value;}
|
|
}
|
|
|
|
internal bool HasExpiration() {
|
|
return _utcExpires < DateTime.MaxValue;
|
|
}
|
|
|
|
internal bool InExpires() {
|
|
return !_expiresEntryRef.IsInvalid;
|
|
}
|
|
|
|
internal byte UsageBucket {
|
|
get {return _usageBucket;}
|
|
}
|
|
|
|
internal UsageEntryRef UsageEntryRef {
|
|
get {return _usageEntryRef;}
|
|
set {_usageEntryRef = value;}
|
|
}
|
|
|
|
internal DateTime UtcLastUsageUpdate {
|
|
get {return _utcLastUpdate;}
|
|
set {_utcLastUpdate = value;}
|
|
}
|
|
|
|
internal bool HasUsage() {
|
|
return _usageBucket != 0xff;
|
|
}
|
|
|
|
internal bool InUsage() {
|
|
return !_usageEntryRef.IsInvalid;
|
|
}
|
|
|
|
internal CacheDependency Dependency {
|
|
get {return _dependency;}
|
|
}
|
|
|
|
internal void MonitorDependencyChanges() {
|
|
// need to protect against the item being closed
|
|
CacheDependency dependency = _dependency;
|
|
if (dependency != null && State == EntryState.AddedToCache) {
|
|
if (!dependency.TakeOwnership()) {
|
|
throw new InvalidOperationException(
|
|
SR.GetString(SR.Cache_dependency_used_more_that_once));
|
|
}
|
|
|
|
dependency.SetCacheDependencyChanged((Object sender, EventArgs args) => {
|
|
DependencyChanged(sender, args);
|
|
});
|
|
}
|
|
}
|
|
|
|
/*
|
|
* The entry has changed, so remove ourselves from the cache.
|
|
*/
|
|
void DependencyChanged(Object sender, EventArgs e) {
|
|
if (State == EntryState.AddedToCache) {
|
|
_cache.Remove(this, CacheItemRemovedReason.DependencyChanged);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Helper to call the on-remove callback
|
|
*/
|
|
|
|
private void CallCacheItemRemovedCallback(CacheItemRemovedCallback callback, CacheItemRemovedReason reason) {
|
|
if (IsPublic) {
|
|
try {
|
|
// for public need to impersonate if called outside of request context
|
|
if (HttpContext.Current == null) {
|
|
using (new ApplicationImpersonationContext()) {
|
|
callback(_key, _value, reason);
|
|
}
|
|
}
|
|
else {
|
|
callback(_key, _value, reason);
|
|
}
|
|
}
|
|
catch (Exception e) {
|
|
// for public need to report application error
|
|
HttpApplicationFactory.RaiseError(e);
|
|
|
|
try {
|
|
WebBaseEvent.RaiseRuntimeError(e, this);
|
|
}
|
|
catch {
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
// for private items just make the call and eat any exceptions
|
|
try {
|
|
using (new ApplicationImpersonationContext()) {
|
|
callback(_key, _value, reason);
|
|
}
|
|
}
|
|
catch {
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Close the item to complete its removal from cache.
|
|
*
|
|
* @param reason The reason the item is removed.
|
|
*/
|
|
internal void Close(CacheItemRemovedReason reason) {
|
|
Debug.Assert(State == EntryState.RemovedFromCache, "State == EntryState.RemovedFromCache");
|
|
State = EntryState.Closed;
|
|
|
|
object onRemovedTargets = null;
|
|
object[] targets = null;
|
|
|
|
lock (this) {
|
|
if (_onRemovedTargets != null) {
|
|
onRemovedTargets = _onRemovedTargets;
|
|
if (onRemovedTargets is Hashtable) {
|
|
ICollection col = ((Hashtable) onRemovedTargets).Keys;
|
|
targets = new object[col.Count];
|
|
col.CopyTo(targets, 0);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (onRemovedTargets != null) {
|
|
if (targets != null) {
|
|
foreach (object target in targets) {
|
|
if (target is CacheDependency) {
|
|
((CacheDependency)target).ItemRemoved();
|
|
}
|
|
else {
|
|
CallCacheItemRemovedCallback((CacheItemRemovedCallback) target, reason);
|
|
}
|
|
}
|
|
}
|
|
else if (onRemovedTargets is CacheItemRemovedCallback) {
|
|
CallCacheItemRemovedCallback((CacheItemRemovedCallback) onRemovedTargets, reason);
|
|
}
|
|
else {
|
|
((CacheDependency) onRemovedTargets).ItemRemoved();
|
|
}
|
|
}
|
|
|
|
if (_dependency != null) {
|
|
_dependency.DisposeInternal();
|
|
}
|
|
}
|
|
|
|
#if DBG
|
|
internal /*public*/ string DebugDescription(string indent) {
|
|
StringBuilder sb = new StringBuilder();
|
|
String nlindent = "\n" + indent + " ";
|
|
|
|
sb.Append(indent + "CacheItem");
|
|
sb.Append(nlindent); sb.Append("_key="); sb.Append(_key);
|
|
sb.Append(nlindent); sb.Append("_value="); sb.Append(Debug.GetDescription(_value, indent));
|
|
sb.Append(nlindent); sb.Append("_utcExpires="); sb.Append(Debug.FormatUtcDate(_utcExpires));
|
|
sb.Append(nlindent); sb.Append("_bits=0x"); sb.Append(((int)_bits).ToString("x", CultureInfo.InvariantCulture));
|
|
sb.Append("\n");
|
|
|
|
return sb.ToString();
|
|
}
|
|
#endif
|
|
|
|
internal void AddDependent(CacheDependency dependency) {
|
|
lock (this) {
|
|
if (_onRemovedTargets == null) {
|
|
_onRemovedTargets = dependency;
|
|
}
|
|
else if (_onRemovedTargets is Hashtable) {
|
|
Hashtable h = (Hashtable) _onRemovedTargets;
|
|
h[dependency] = dependency;
|
|
}
|
|
else {
|
|
Hashtable h = new Hashtable(2);
|
|
h[_onRemovedTargets] = _onRemovedTargets;
|
|
h[dependency] = dependency;
|
|
_onRemovedTargets = h;
|
|
}
|
|
}
|
|
}
|
|
|
|
internal void RemoveDependent(CacheDependency dependency) {
|
|
lock (this) {
|
|
if (_onRemovedTargets != null) {
|
|
if (_onRemovedTargets == dependency) {
|
|
_onRemovedTargets = null;
|
|
}
|
|
else if (_onRemovedTargets is Hashtable) {
|
|
Hashtable h = (Hashtable)_onRemovedTargets;
|
|
h.Remove(dependency);
|
|
if (h.Count == 0) {
|
|
_onRemovedTargets = null;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#if USE_MEMORY_CACHE
|
|
internal CacheItemRemovedCallback CacheItemRemovedCallback {
|
|
get {
|
|
CacheItemRemovedCallback callback = null;
|
|
lock (this) {
|
|
if (_onRemovedTargets != null) {
|
|
if (_onRemovedTargets is Hashtable) {
|
|
foreach (DictionaryEntry e in (Hashtable)_onRemovedTargets) {
|
|
callback = e.Value as CacheItemRemovedCallback;
|
|
break;
|
|
}
|
|
}
|
|
else {
|
|
callback = _onRemovedTargets as CacheItemRemovedCallback;
|
|
}
|
|
}
|
|
}
|
|
return callback;
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
}
|