|
// <copyright file="MemoryCacheEntry.cs" company="Microsoft">
// Copyright (c) 2009 Microsoft Corporation. All rights reserved.
// </copyright>
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics.CodeAnalysis;
using System.Threading;
namespace System.Runtime.Caching {
internal class MemoryCacheEntry: MemoryCacheKey {
private Object _value;
private DateTime _utcCreated;
private int _state;
// expiration
private DateTime _utcAbsExp;
private TimeSpan _slidingExp;
private ExpiresEntryRef _expiresEntryRef;
private byte _expiresBucket; // index of the expiration list (bucket)
// usage
private byte _usageBucket; // index of the usage list (== priority-1)
private UsageEntryRef _usageEntryRef; // ref into the usage list
private DateTime _utcLastUpdateUsage; // time we last updated usage
private CacheEntryRemovedCallback _callback;
private SeldomUsedFields _fields; // optimization to reduce workingset when the entry hasn't any dependencies
class SeldomUsedFields {
internal Collection<ChangeMonitor> _dependencies; // the entry's dependency needs to be disposed when the entry is released
internal Dictionary<MemoryCacheEntryChangeMonitor, MemoryCacheEntryChangeMonitor> _dependents; // dependents must be notified when this entry is removed
internal MemoryCache _cache;
internal Tuple<MemoryCacheStore, MemoryCacheEntry> _updateSentinel; // the MemoryCacheEntry (and its associated store) of the OnUpdateSentinel for this entry, if there is one
}
internal Object Value {
get { return _value; }
}
internal bool HasExpiration() {
return _utcAbsExp < DateTime.MaxValue;
}
internal DateTime UtcAbsExp {
get { return _utcAbsExp; }
set { _utcAbsExp = value; }
}
internal DateTime UtcCreated {
get { return _utcCreated; }
}
internal ExpiresEntryRef ExpiresEntryRef {
get { return _expiresEntryRef; }
set { _expiresEntryRef = value; }
}
internal byte ExpiresBucket {
get { return _expiresBucket; }
set { _expiresBucket = value; }
}
internal bool InExpires() {
return !_expiresEntryRef.IsInvalid;
}
internal TimeSpan SlidingExp {
get { return _slidingExp; }
}
internal EntryState State {
get { return (EntryState)_state; }
set { _state = (int)value; }
}
internal byte UsageBucket {
get { return _usageBucket; }
}
internal UsageEntryRef UsageEntryRef {
get { return _usageEntryRef; }
set { _usageEntryRef = value; }
}
[SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Grandfathered suppression from original caching code checkin")]
internal DateTime UtcLastUpdateUsage {
get { return _utcLastUpdateUsage; }
set { _utcLastUpdateUsage = value; }
}
internal MemoryCacheEntry(String key,
Object value,
DateTimeOffset absExp,
TimeSpan slidingExp,
CacheItemPriority priority,
Collection<ChangeMonitor> dependencies,
CacheEntryRemovedCallback removedCallback,
MemoryCache cache) : base(key) {
if (value == null) {
throw new ArgumentNullException("value");
}
_utcCreated = DateTime.UtcNow;
_value = value;
_slidingExp = slidingExp;
if (_slidingExp > TimeSpan.Zero) {
_utcAbsExp = _utcCreated + _slidingExp;
}
else {
_utcAbsExp = absExp.UtcDateTime;
}
_expiresEntryRef = ExpiresEntryRef.INVALID;
_expiresBucket = 0xff;
_usageEntryRef = UsageEntryRef.INVALID;
if (priority == CacheItemPriority.NotRemovable) {
_usageBucket = 0xff;
}
else {
_usageBucket = 0;
}
_callback = removedCallback;
if (dependencies != null) {
_fields = new SeldomUsedFields();
_fields._dependencies = dependencies;
_fields._cache = cache;
}
}
internal void AddDependent(MemoryCache cache, MemoryCacheEntryChangeMonitor dependent) {
lock (this) {
if (State > EntryState.AddedToCache) {
return;
}
if (_fields == null) {
_fields = new SeldomUsedFields();
}
if (_fields._cache == null) {
_fields._cache = cache;
}
if (_fields._dependents == null) {
_fields._dependents = new Dictionary<MemoryCacheEntryChangeMonitor, MemoryCacheEntryChangeMonitor>();
}
_fields._dependents[dependent] = dependent;
}
}
private void CallCacheEntryRemovedCallback(MemoryCache cache, CacheEntryRemovedReason reason) {
if (_callback == null) {
return;
}
CacheEntryRemovedArguments args = new CacheEntryRemovedArguments(cache, reason, new CacheItem(Key, _value));
try {
_callback(args);
}
catch {
//
}
}
internal void CallNotifyOnChanged() {
if (_fields != null && _fields._dependencies != null) {
foreach (ChangeMonitor monitor in _fields._dependencies) {
monitor.NotifyOnChanged(new OnChangedCallback(this.OnDependencyChanged));
}
}
}
internal bool CompareExchangeState(EntryState value, EntryState comparand) {
return (Interlocked.CompareExchange(ref _state, (int)value, (int)comparand) == (int)comparand);
}
// Associates this entry with an update sentinel. If this entry has a sliding expiration, we need to
// touch the sentinel so that it doesn't expire.
internal void ConfigureUpdateSentinel(MemoryCacheStore sentinelStore, MemoryCacheEntry sentinelEntry) {
lock (this) {
if (_fields == null) {
_fields = new SeldomUsedFields();
}
_fields._updateSentinel = Tuple.Create(sentinelStore, sentinelEntry);
}
}
internal bool HasUsage() {
return _usageBucket != 0xff;
}
internal bool InUsage() {
return !_usageEntryRef.IsInvalid;
}
private void OnDependencyChanged(Object state) {
if (State == EntryState.AddedToCache) {
_fields._cache.RemoveEntry(this.Key, this, CacheEntryRemovedReason.ChangeMonitorChanged);
}
}
internal void Release(MemoryCache cache, CacheEntryRemovedReason reason) {
State = EntryState.Closed;
// Are there any cache entries that depend on this entry?
// If so, we need to fire their dependencies.
Dictionary<MemoryCacheEntryChangeMonitor, MemoryCacheEntryChangeMonitor>.KeyCollection deps = null;
// clone the dependents
lock (this) {
if (_fields != null && _fields._dependents != null && _fields._dependents.Count > 0) {
deps = _fields._dependents.Keys;
// set to null so RemoveDependent does not attempt to access it, since we're not
// using a copy of the KeyCollection.
_fields._dependents = null;
Dbg.Assert(_fields._dependents == null, "_fields._dependents == null");
}
}
if (deps != null) {
foreach (MemoryCacheEntryChangeMonitor dependent in deps) {
if (dependent != null) {
dependent.OnCacheEntryReleased();
}
}
}
CallCacheEntryRemovedCallback(cache, reason);
// Dispose any dependencies
if (_fields != null && _fields._dependencies != null) {
foreach (ChangeMonitor monitor in _fields._dependencies) {
monitor.Dispose();
}
}
}
internal void RemoveDependent(MemoryCacheEntryChangeMonitor dependent) {
lock (this) {
if (_fields != null && _fields._dependents != null) {
_fields._dependents.Remove(dependent);
}
}
}
internal void UpdateSlidingExp(DateTime utcNow, CacheExpires expires) {
if (_slidingExp > TimeSpan.Zero) {
DateTime utcNewExpires = utcNow + _slidingExp;
if (utcNewExpires - _utcAbsExp >= CacheExpires.MIN_UPDATE_DELTA || utcNewExpires < _utcAbsExp) {
expires.UtcUpdate(this, utcNewExpires);
}
}
}
internal void UpdateSlidingExpForUpdateSentinel() {
// We don't need a lock to get information about the update sentinel
SeldomUsedFields fields = _fields;
if (fields != null) {
Tuple<MemoryCacheStore, MemoryCacheEntry> sentinelInfo = fields._updateSentinel;
// touch the update sentinel to keep it from expiring
if (sentinelInfo != null) {
MemoryCacheStore sentinelStore = sentinelInfo.Item1;
MemoryCacheEntry sentinelEntry = sentinelInfo.Item2;
sentinelStore.UpdateExpAndUsage(sentinelEntry, updatePerfCounters: false); // perf counters shouldn't be polluted by touching update sentinel entry
}
}
}
internal void UpdateUsage(DateTime utcNow, CacheUsage usage) {
// update, but not more frequently than once per second.
if (InUsage() && _utcLastUpdateUsage < utcNow - CacheUsage.CORRELATED_REQUEST_TIMEOUT) {
_utcLastUpdateUsage = utcNow;
usage.Update(this);
if (_fields != null && _fields._dependencies != null) {
foreach (ChangeMonitor monitor in _fields._dependencies) {
MemoryCacheEntryChangeMonitor m = monitor as MemoryCacheEntryChangeMonitor;
if (m == null) {
continue;
}
foreach (MemoryCacheEntry e in m.Dependencies) {
MemoryCacheStore store = e._fields._cache.GetStore(e);
e.UpdateUsage(utcNow, store.Usage);
}
}
}
}
}
}
}
|