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-07-08 11:07:53 +02: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 ;
2015-07-08 11:07:53 +02: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-07-08 11:07:53 +02:00
InstanceId = Guid . NewGuid ();
2013-08-12 15:06:12 +02:00
}
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
2015-05-19 09:16:05 +02:00
var value = DictionaryCacheProviderBase . 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
2015-05-19 09:16:05 +02:00
var value = DictionaryCacheProviderBase . GetSafeLazyValue (( Lazy < object >) x . Value , true );
2015-07-08 11:07:53 +02: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
2015-05-19 09:16:05 +02:00
var value = DictionaryCacheProviderBase . GetSafeLazyValue (( Lazy < object >) x . Value , true );
2014-05-27 12:16:41 +02:00
if ( value == null ) return true ;
2015-07-08 11:07:53 +02: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
2015-05-19 09:16:05 +02:00
. Select ( x => DictionaryCacheProviderBase . GetSafeLazyValue (( Lazy < object >) x . Value )) // return exceptions as null
2014-07-10 10:24:04 +02:00
. 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
2015-05-19 09:16:05 +02:00
. Select ( x => DictionaryCacheProviderBase . GetSafeLazyValue (( Lazy < object >) x . Value )) // return exceptions as null
2014-07-10 10:24:04 +02:00
. 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
}
2015-05-19 09:16:05 +02:00
return result == null ? null : DictionaryCacheProviderBase . 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 >;
2015-05-19 09:16:05 +02:00
if ( result == null || DictionaryCacheProviderBase . GetSafeLazyValue ( result , true ) == null ) // get non-created as NonCreatedValue & exceptions as null
2013-08-12 15:06:12 +02:00
{
2015-05-19 09:16:05 +02:00
result = DictionaryCacheProviderBase . GetSafeLazy ( getCacheItem );
2014-05-16 16:48:31 +02:00
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
2015-05-19 09:16:05 +02:00
//return result.Value;
var value = result . Value ; // will not throw (safe lazy)
var eh = value as DictionaryCacheProviderBase . ExceptionHolder ;
if ( eh != null ) throw eh . Exception ; // throw once!
return 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
2015-05-19 09:16:05 +02:00
var result = DictionaryCacheProviderBase . GetSafeLazy ( 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
}