2016-08-03 10:59:49 +00:00
// <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 ;
2018-01-24 17:04:36 +00:00
using System.Threading ;
2016-08-03 10:59:49 +00:00
namespace System.Runtime.Caching {
internal class MemoryCacheEntry : MemoryCacheKey {
private Object _value ;
private DateTime _utcCreated ;
2018-01-24 17:04:36 +00:00
private int _state ;
2016-08-03 10:59:49 +00:00
// 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 {
2018-01-24 17:04:36 +00:00
get { return ( EntryState ) _state ; }
set { _state = ( int ) value ; }
2016-08-03 10:59:49 +00:00
}
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 ) ) ;
}
}
}
2018-01-24 17:04:36 +00:00
internal bool CompareExchangeState ( EntryState value , EntryState comparand ) {
return ( Interlocked . CompareExchange ( ref _state , ( int ) value , ( int ) comparand ) = = ( int ) comparand ) ;
}
2016-08-03 10:59:49 +00:00
// 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 ) ;
}
}
}
}
}
}
}