2013-08-12 15:06:12 +02:00
using System ;
using System.Collections.Generic ;
using System.ComponentModel ;
using System.Linq ;
using System.Runtime.Caching ;
using System.Text.RegularExpressions ;
using System.Threading ;
using System.Web.Caching ;
using Umbraco.Core.Logging ;
using CacheItemPriority = System . Web . Caching . CacheItemPriority ;
namespace Umbraco.Core.Cache
{
/// <summary>
/// A cache provider that wraps the logic of a System.Runtime.Caching.ObjectCache
/// </summary>
internal class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider
{
2015-01-29 16:40:52 +11:00
2014-05-22 09:36:08 +02:00
private readonly ReaderWriterLockSlim _locker = new ReaderWriterLockSlim ( LockRecursionPolicy . SupportsRecursion ) ;
2013-08-12 15:06:12 +02:00
internal ObjectCache MemoryCache ;
2014-07-10 10:24:04 +02:00
// an object that represent a value that has not been created yet
protected readonly object ValueNotCreated = new object ( ) ;
2015-01-29 16:40:52 +11:00
/// <summary>
/// Used for debugging
/// </summary>
internal Guid InstanceId { get ; private set ; }
2013-08-12 15:06:12 +02:00
public ObjectCacheRuntimeCacheProvider ( )
{
MemoryCache = new MemoryCache ( "in-memory" ) ;
2015-01-29 16:40:52 +11:00
InstanceId = Guid . NewGuid ( ) ;
2013-08-12 15:06:12 +02:00
}
2014-07-10 10:24:04 +02:00
protected object GetSafeLazyValue ( Lazy < object > lazy , bool onlyIfValueIsCreated = false )
{
try
{
// if onlyIfValueIsCreated, do not trigger value creation
// must return something, though, to differenciate from null values
if ( onlyIfValueIsCreated & & lazy . IsValueCreated = = false ) return ValueNotCreated ;
return lazy . Value ;
}
catch
{
return null ;
}
}
2014-05-27 12:16:41 +02:00
#region Clear
2013-08-12 15:06:12 +02:00
public virtual void ClearAllCache ( )
{
2014-05-22 09:36:08 +02:00
using ( new WriteLock ( _locker ) )
2013-08-12 15:06:12 +02:00
{
MemoryCache . DisposeIfDisposable ( ) ;
MemoryCache = new MemoryCache ( "in-memory" ) ;
}
}
public virtual void ClearCacheItem ( string key )
{
2014-05-22 09:36:08 +02:00
using ( new WriteLock ( _locker ) )
2013-08-12 15:06:12 +02:00
{
if ( MemoryCache [ key ] = = null ) return ;
MemoryCache . Remove ( key ) ;
}
}
public virtual void ClearCacheObjectTypes ( string typeName )
{
2014-05-22 09:36:08 +02:00
using ( new WriteLock ( _locker ) )
2013-08-12 15:06:12 +02:00
{
2014-05-26 15:56:29 +02:00
foreach ( var key in MemoryCache
. Where ( x = >
{
// x.Value is Lazy<object> and not null, its value may be null
2014-05-27 12:16:41 +02:00
// remove null values as well, does not hurt
2014-07-10 10:24:04 +02:00
// get non-created as NonCreatedValue & exceptions as null
var value = GetSafeLazyValue ( ( Lazy < object > ) x . Value , true ) ;
2014-05-27 12:16:41 +02:00
return value = = null | | value . GetType ( ) . ToString ( ) . InvariantEquals ( typeName ) ;
2014-05-26 15:56:29 +02:00
} )
. Select ( x = > x . Key )
. ToArray ( ) ) // ToArray required to remove
MemoryCache . Remove ( key ) ;
2013-09-18 12:27:38 +02:00
}
}
public virtual void ClearCacheObjectTypes < T > ( )
{
2014-05-22 09:36:08 +02:00
using ( new WriteLock ( _locker ) )
2013-09-18 12:27:38 +02:00
{
var typeOfT = typeof ( T ) ;
2014-05-26 15:56:29 +02:00
foreach ( var key in MemoryCache
. Where ( x = >
{
// x.Value is Lazy<object> and not null, its value may be null
2014-05-27 12:16:41 +02:00
// remove null values as well, does not hurt
2014-07-10 10:24:04 +02:00
// get non-created as NonCreatedValue & exceptions as null
var value = GetSafeLazyValue ( ( Lazy < object > ) x . Value , true ) ;
2015-01-29 16:40:52 +11:00
//TODO: waiting on a response for this comment: https://github.com/umbraco/Umbraco-CMS/commit/c2db7b2b9b78847a828512818e79492ecc24ac7c#commitcomment-9492329
// until then we will check if 'T' is an interface and if so we will use the 'is' clause,
// otherwise we do an exact match.
return value = = null | |
( typeOfT . IsInterface
? ( value is T )
: value . GetType ( ) = = typeOfT ) ;
2014-05-26 15:56:29 +02:00
} )
. Select ( x = > x . Key )
. ToArray ( ) ) // ToArray required to remove
MemoryCache . Remove ( key ) ;
2013-09-18 12:27:38 +02:00
}
}
public virtual void ClearCacheObjectTypes < T > ( Func < string , T , bool > predicate )
{
2014-05-22 09:36:08 +02:00
using ( new WriteLock ( _locker ) )
2013-09-18 12:27:38 +02:00
{
var typeOfT = typeof ( T ) ;
2014-05-26 15:56:29 +02:00
foreach ( var key in MemoryCache
. Where ( x = >
{
// x.Value is Lazy<object> and not null, its value may be null
2014-05-27 12:16:41 +02:00
// remove null values as well, does not hurt
2014-07-10 10:24:04 +02:00
// get non-created as NonCreatedValue & exceptions as null
var value = GetSafeLazyValue ( ( Lazy < object > ) x . Value , true ) ;
2014-05-27 12:16:41 +02:00
if ( value = = null ) return true ;
2015-01-29 16:40:52 +11:00
//TODO: waiting on a response for this comment: https://github.com/umbraco/Umbraco-CMS/commit/c2db7b2b9b78847a828512818e79492ecc24ac7c#commitcomment-9492329
// until then we will check if 'T' is an interface and if so we will use the 'is' clause,
// otherwise we do an exact match.
return ( ( typeOfT . IsInterface & & value is T ) | | ( value . GetType ( ) = = typeOfT ) )
& & predicate ( x . Key , ( T ) value ) ;
2014-05-26 15:56:29 +02:00
} )
. Select ( x = > x . Key )
. ToArray ( ) ) // ToArray required to remove
MemoryCache . Remove ( key ) ;
2013-08-12 15:06:12 +02:00
}
}
public virtual void ClearCacheByKeySearch ( string keyStartsWith )
{
2014-05-22 09:36:08 +02:00
using ( new WriteLock ( _locker ) )
2013-08-12 15:06:12 +02:00
{
2014-05-26 15:56:29 +02:00
foreach ( var key in MemoryCache
. Where ( x = > x . Key . InvariantStartsWith ( keyStartsWith ) )
. Select ( x = > x . Key )
. ToArray ( ) ) // ToArray required to remove
MemoryCache . Remove ( key ) ;
2013-08-12 15:06:12 +02:00
}
}
public virtual void ClearCacheByKeyExpression ( string regexString )
{
2014-05-22 09:36:08 +02:00
using ( new WriteLock ( _locker ) )
2013-08-12 15:06:12 +02:00
{
2014-05-26 15:56:29 +02:00
foreach ( var key in MemoryCache
. Where ( x = > Regex . IsMatch ( x . Key , regexString ) )
. Select ( x = > x . Key )
. ToArray ( ) ) // ToArray required to remove
MemoryCache . Remove ( key ) ;
2013-08-12 15:06:12 +02:00
}
}
2014-05-27 12:16:41 +02:00
#endregion
#region Get
public IEnumerable < object > GetCacheItemsByKeySearch ( string keyStartsWith )
2013-08-12 15:06:12 +02:00
{
2014-07-10 10:24:04 +02:00
KeyValuePair < string , object > [ ] entries ;
2014-05-22 09:36:08 +02:00
using ( new ReadLock ( _locker ) )
{
2014-07-10 10:24:04 +02:00
entries = MemoryCache
2014-05-22 09:36:08 +02:00
. Where ( x = > x . Key . InvariantStartsWith ( keyStartsWith ) )
2014-07-10 10:24:04 +02:00
. ToArray ( ) ; // evaluate while locked
2014-05-22 09:36:08 +02:00
}
2014-07-10 10:24:04 +02:00
return entries
. Select ( x = > GetSafeLazyValue ( ( Lazy < object > ) x . Value ) ) // return exceptions as null
. Where ( x = > x ! = null ) // backward compat, don't store null values in the cache
. ToList ( ) ;
2013-08-12 15:06:12 +02:00
}
2014-04-17 18:10:42 +10:00
public IEnumerable < object > GetCacheItemsByKeyExpression ( string regexString )
{
2014-07-10 10:24:04 +02:00
KeyValuePair < string , object > [ ] entries ;
2014-05-22 09:36:08 +02:00
using ( new ReadLock ( _locker ) )
{
2014-07-10 10:24:04 +02:00
entries = MemoryCache
2014-05-22 09:36:08 +02:00
. Where ( x = > Regex . IsMatch ( x . Key , regexString ) )
2014-07-10 10:24:04 +02:00
. ToArray ( ) ; // evaluate while locked
2014-05-22 09:36:08 +02:00
}
2014-07-10 10:24:04 +02:00
return entries
. Select ( x = > GetSafeLazyValue ( ( Lazy < object > ) x . Value ) ) // return exceptions as null
. Where ( x = > x ! = null ) // backward compat, don't store null values in the cache
. ToList ( ) ;
2014-04-17 18:10:42 +10:00
}
2014-05-27 12:16:41 +02:00
public object GetCacheItem ( string cacheKey )
2013-08-12 15:06:12 +02:00
{
2014-07-10 10:24:04 +02:00
Lazy < object > result ;
2014-05-22 09:36:08 +02:00
using ( new ReadLock ( _locker ) )
{
2014-07-10 10:24:04 +02:00
result = MemoryCache . Get ( cacheKey ) as Lazy < object > ; // null if key not found
2014-05-22 09:36:08 +02:00
}
2014-07-10 10:24:04 +02:00
return result = = null ? null : GetSafeLazyValue ( result ) ; // return exceptions as null
2013-08-12 15:06:12 +02:00
}
2014-05-27 12:16:41 +02:00
public object GetCacheItem ( string cacheKey , Func < object > getCacheItem )
2013-08-12 15:06:12 +02:00
{
return GetCacheItem ( cacheKey , getCacheItem , null ) ;
}
2014-05-27 12:16:41 +02:00
public object GetCacheItem ( string cacheKey , Func < object > getCacheItem , TimeSpan ? timeout , bool isSliding = false , CacheItemPriority priority = CacheItemPriority . Normal , CacheItemRemovedCallback removedCallback = null , string [ ] dependentFiles = null )
2013-08-12 15:06:12 +02:00
{
2014-05-16 16:48:31 +02:00
// see notes in HttpRuntimeCacheProvider
Lazy < object > result ;
2014-05-22 09:36:08 +02:00
using ( var lck = new UpgradeableReadLock ( _locker ) )
2013-08-12 15:06:12 +02:00
{
2014-05-16 16:48:31 +02:00
result = MemoryCache . Get ( cacheKey ) as Lazy < object > ;
2014-07-10 10:24:04 +02:00
if ( result = = null | | GetSafeLazyValue ( result , true ) = = null ) // get non-created as NonCreatedValue & exceptions as null
2013-08-12 15:06:12 +02:00
{
2014-05-16 16:48:31 +02:00
result = new Lazy < object > ( getCacheItem ) ;
var policy = GetPolicy ( timeout , isSliding , removedCallback , dependentFiles ) ;
2014-07-10 10:24:04 +02:00
lck . UpgradeToWriteLock ( ) ;
2014-05-16 16:48:31 +02:00
MemoryCache . Set ( cacheKey , result , policy ) ;
2013-08-12 15:06:12 +02:00
}
}
2014-05-16 16:48:31 +02:00
return result . Value ;
2013-08-12 15:06:12 +02:00
}
2014-05-27 12:16:41 +02:00
#endregion
#region Insert
2013-08-12 15:06:12 +02:00
public void InsertCacheItem ( string cacheKey , Func < object > getCacheItem , TimeSpan ? timeout = null , bool isSliding = false , CacheItemPriority priority = CacheItemPriority . Normal , CacheItemRemovedCallback removedCallback = null , string [ ] dependentFiles = null )
{
2014-05-26 15:56:29 +02:00
// NOTE - here also we must insert a Lazy<object> but we can evaluate it right now
// and make sure we don't store a null value.
2014-05-16 16:48:31 +02:00
var result = new Lazy < object > ( getCacheItem ) ;
2014-05-22 09:36:08 +02:00
var value = result . Value ; // force evaluation now
2014-05-26 15:56:29 +02:00
if ( value = = null ) return ; // do not store null values (backward compat)
2014-05-16 16:48:31 +02:00
var policy = GetPolicy ( timeout , isSliding , removedCallback , dependentFiles ) ;
MemoryCache . Set ( cacheKey , result , policy ) ;
2013-08-12 15:06:12 +02:00
}
2014-05-27 12:16:41 +02:00
#endregion
2013-08-12 15:06:12 +02:00
private static CacheItemPolicy GetPolicy ( TimeSpan ? timeout = null , bool isSliding = false , CacheItemRemovedCallback removedCallback = null , string [ ] dependentFiles = null )
{
var absolute = isSliding ? ObjectCache . InfiniteAbsoluteExpiration : ( timeout = = null ? ObjectCache . InfiniteAbsoluteExpiration : DateTime . Now . Add ( timeout . Value ) ) ;
var sliding = isSliding = = false ? ObjectCache . NoSlidingExpiration : ( timeout ? ? ObjectCache . NoSlidingExpiration ) ;
var policy = new CacheItemPolicy
{
AbsoluteExpiration = absolute ,
SlidingExpiration = sliding
} ;
if ( dependentFiles ! = null & & dependentFiles . Any ( ) )
{
policy . ChangeMonitors . Add ( new HostFileChangeMonitor ( dependentFiles . ToList ( ) ) ) ;
}
if ( removedCallback ! = null )
{
policy . RemovedCallback = arguments = >
{
//convert the reason
var reason = CacheItemRemovedReason . Removed ;
switch ( arguments . RemovedReason )
{
case CacheEntryRemovedReason . Removed :
reason = CacheItemRemovedReason . Removed ;
break ;
case CacheEntryRemovedReason . Expired :
reason = CacheItemRemovedReason . Expired ;
break ;
case CacheEntryRemovedReason . Evicted :
reason = CacheItemRemovedReason . Underused ;
break ;
case CacheEntryRemovedReason . ChangeMonitorChanged :
reason = CacheItemRemovedReason . Expired ;
break ;
case CacheEntryRemovedReason . CacheSpecificEviction :
reason = CacheItemRemovedReason . Underused ;
break ;
}
//call the callback
removedCallback ( arguments . CacheItem . Key , arguments . CacheItem . Value , reason ) ;
} ;
}
return policy ;
}
}
2013-08-08 19:46:58 +10:00
}