2013-08-12 15:06:12 +02:00
|
|
|
|
using System;
|
|
|
|
|
|
using System.Collections.Generic;
|
|
|
|
|
|
using System.Linq;
|
|
|
|
|
|
using System.Runtime.Caching;
|
|
|
|
|
|
using System.Text.RegularExpressions;
|
|
|
|
|
|
using System.Threading;
|
|
|
|
|
|
using System.Web.Caching;
|
2016-05-18 23:34:56 +02:00
|
|
|
|
using Umbraco.Core.Plugins;
|
2013-08-12 15:06:12 +02:00
|
|
|
|
using CacheItemPriority = System.Web.Caching.CacheItemPriority;
|
|
|
|
|
|
|
|
|
|
|
|
namespace Umbraco.Core.Cache
|
|
|
|
|
|
{
|
|
|
|
|
|
/// <summary>
|
2016-07-06 18:01:59 +02:00
|
|
|
|
/// Represents a cache provider that caches item in a <see cref="MemoryCache"/>.
|
2013-08-12 15:06:12 +02:00
|
|
|
|
/// A cache provider that wraps the logic of a System.Runtime.Caching.ObjectCache
|
|
|
|
|
|
/// </summary>
|
2016-07-06 18:01:59 +02:00
|
|
|
|
/// <remarks>The <see cref="MemoryCache"/> is created with name "in-memory". That name is
|
|
|
|
|
|
/// used to retrieve configuration options. It does not identify the memory cache, i.e.
|
|
|
|
|
|
/// each instance of this class has its own, independent, memory cache.</remarks>
|
|
|
|
|
|
public class ObjectCacheRuntimeCacheProvider : IRuntimeCacheProvider
|
2013-08-12 15:06:12 +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-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-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);
|
2015-07-08 18:14:31 +02:00
|
|
|
|
}
|
2013-08-12 15:06:12 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public virtual void ClearCacheObjectTypes(string typeName)
|
|
|
|
|
|
{
|
2015-01-30 18:28:20 +01:00
|
|
|
|
var type = TypeFinder.GetTypeByName(typeName);
|
2015-01-30 10:51:50 +01:00
|
|
|
|
if (type == null) return;
|
|
|
|
|
|
var isInterface = type.IsInterface;
|
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);
|
2015-01-30 10:51:50 +01:00
|
|
|
|
|
|
|
|
|
|
// if T is an interface remove anything that implements that interface
|
|
|
|
|
|
// otherwise remove exact types (not inherited types)
|
|
|
|
|
|
return value == null || (isInterface ? (type.IsInstanceOfType(value)) : (value.GetType() == type));
|
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);
|
2015-01-30 10:51:50 +01:00
|
|
|
|
var isInterface = typeOfT.IsInterface;
|
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-01-29 16:40:52 +11:00
|
|
|
|
|
2015-01-30 10:51:50 +01:00
|
|
|
|
// if T is an interface remove anything that implements that interface
|
|
|
|
|
|
// otherwise remove exact types (not inherited types)
|
|
|
|
|
|
return value == null || (isInterface ? (value is T) : (value.GetType() == typeOfT));
|
2015-01-29 16:40:52 +11:00
|
|
|
|
|
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);
|
2015-01-30 10:51:50 +01:00
|
|
|
|
var isInterface = typeOfT.IsInterface;
|
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-01-29 16:40:52 +11:00
|
|
|
|
|
2015-01-30 10:51:50 +01:00
|
|
|
|
// if T is an interface remove anything that implements that interface
|
|
|
|
|
|
// otherwise remove exact types (not inherited types)
|
|
|
|
|
|
return (isInterface ? (value is T) : (value.GetType() == typeOfT))
|
2015-01-29 16:40:52 +11:00
|
|
|
|
&& 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);
|
2015-07-08 18:14:31 +02:00
|
|
|
|
}
|
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);
|
2015-07-08 18:14:31 +02:00
|
|
|
|
}
|
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);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-07-08 18:14:31 +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();
|
2015-05-21 17:04:14 +10:00
|
|
|
|
//NOTE: This does an add or update
|
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);
|
2015-05-21 17:04:14 +10:00
|
|
|
|
//NOTE: This does an add or update
|
2014-05-16 16:48:31 +02:00
|
|
|
|
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()));
|
|
|
|
|
|
}
|
2015-07-08 18:14:31 +02:00
|
|
|
|
|
2013-08-12 15:06:12 +02:00
|
|
|
|
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
|
|
|
|
}
|