diff --git a/src/Umbraco.Core/Cache/CacheProviderBase.cs b/src/Umbraco.Core/Cache/CacheProviderBase.cs index fafa0939f8..026f6f9dbc 100644 --- a/src/Umbraco.Core/Cache/CacheProviderBase.cs +++ b/src/Umbraco.Core/Cache/CacheProviderBase.cs @@ -1,12 +1,10 @@ using System; using System.Collections.Generic; -using System.Text; -using System.Web.Caching; namespace Umbraco.Core.Cache { /// - /// An abstract class for implementing a cache helper + /// An abstract class for implementing a basic cache provider /// /// /// THIS MUST REMAIN INTERNAL UNTIL WE STREAMLINE HOW ALL CACHE IS HANDLED, WE NEED TO SUPPORT HTTP RUNTIME CACHE, IN MEMORY CACHE, ETC... @@ -22,13 +20,5 @@ namespace Umbraco.Core.Cache public abstract IEnumerable GetCacheItemsByKeySearch(string keyStartsWith); public abstract T GetCacheItem(string cacheKey); public abstract T GetCacheItem(string cacheKey, Func getCacheItem); - public abstract T GetCacheItem(string cacheKey, TimeSpan? timeout, Func getCacheItem); - public abstract T GetCacheItem(string cacheKey, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem); - public abstract T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem); - public abstract T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); - public abstract void InsertCacheItem(string cacheKey, CacheItemPriority priority, Func getCacheItem); - public abstract void InsertCacheItem(string cacheKey, CacheItemPriority priority, TimeSpan? timeout, Func getCacheItem); - public abstract void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); - public abstract void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); } -} +} \ No newline at end of file diff --git a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs index 681dc8fab4..e574f38454 100644 --- a/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Cache/HttpRuntimeCacheProvider.cs @@ -11,7 +11,7 @@ namespace Umbraco.Core.Cache /// /// A CacheProvider that wraps the logic of the HttpRuntime.Cache /// - internal class HttpRuntimeCacheProvider : CacheProviderBase + internal class HttpRuntimeCacheProvider : RuntimeCacheProviderBase { private readonly System.Web.Caching.Cache _cache; private static readonly object Locker = new object(); diff --git a/src/Umbraco.Core/Cache/NullCacheProvider.cs b/src/Umbraco.Core/Cache/NullCacheProvider.cs index 3a41ae2a77..3d1bbaa79d 100644 --- a/src/Umbraco.Core/Cache/NullCacheProvider.cs +++ b/src/Umbraco.Core/Cache/NullCacheProvider.cs @@ -5,7 +5,7 @@ using System.Web.Caching; namespace Umbraco.Core.Cache { - internal class NullCacheProvider : CacheProviderBase + internal class NullCacheProvider : RuntimeCacheProviderBase { public override void ClearAllCache() { diff --git a/src/Umbraco.Core/Cache/RuntimeCacheProviderBase.cs b/src/Umbraco.Core/Cache/RuntimeCacheProviderBase.cs new file mode 100644 index 0000000000..9dddb4c576 --- /dev/null +++ b/src/Umbraco.Core/Cache/RuntimeCacheProviderBase.cs @@ -0,0 +1,24 @@ +using System; +using System.Text; +using System.Web.Caching; + +namespace Umbraco.Core.Cache +{ + /// + /// An abstract class for implementing a runtime cache provider + /// + /// + /// THIS MUST REMAIN INTERNAL UNTIL WE STREAMLINE HOW ALL CACHE IS HANDLED, WE NEED TO SUPPORT HTTP RUNTIME CACHE, IN MEMORY CACHE, ETC... + /// + internal abstract class RuntimeCacheProviderBase : CacheProviderBase + { + public abstract T GetCacheItem(string cacheKey, TimeSpan? timeout, Func getCacheItem); + public abstract T GetCacheItem(string cacheKey, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem); + public abstract T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, TimeSpan? timeout, Func getCacheItem); + public abstract T GetCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); + public abstract void InsertCacheItem(string cacheKey, CacheItemPriority priority, Func getCacheItem); + public abstract void InsertCacheItem(string cacheKey, CacheItemPriority priority, TimeSpan? timeout, Func getCacheItem); + public abstract void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); + public abstract void InsertCacheItem(string cacheKey, CacheItemPriority priority, CacheItemRemovedCallback refreshAction, CacheDependency cacheDependency, TimeSpan? timeout, Func getCacheItem); + } +} diff --git a/src/Umbraco.Core/Cache/StaticCacheProvider.cs b/src/Umbraco.Core/Cache/StaticCacheProvider.cs new file mode 100644 index 0000000000..0d0d2f288c --- /dev/null +++ b/src/Umbraco.Core/Cache/StaticCacheProvider.cs @@ -0,0 +1,102 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text.RegularExpressions; +using System.Web.Caching; + +namespace Umbraco.Core.Cache +{ + /// + /// A cache provider that statically caches everything in an in memory dictionary + /// + internal class StaticCacheProvider : CacheProviderBase + { + private readonly ConcurrentDictionary _staticCache = new ConcurrentDictionary(); + + public override void ClearAllCache() + { + _staticCache.Clear(); + } + + public override void ClearCacheItem(string key) + { + object val; + _staticCache.TryRemove(key, out val); + } + + public override void ClearCacheObjectTypes(string typeName) + { + foreach (var key in _staticCache.Keys) + { + if (_staticCache[key] != null + && _staticCache[key].GetType().ToString().InvariantEquals(typeName)) + { + object val; + _staticCache.TryRemove(key, out val); + } + } + } + + public override void ClearCacheObjectTypes() + { + foreach (var key in _staticCache.Keys) + { + if (_staticCache[key] != null + && _staticCache[key].GetType() == typeof(T)) + { + object val; + _staticCache.TryRemove(key, out val); + } + } + } + + public override void ClearCacheByKeySearch(string keyStartsWith) + { + foreach (var key in _staticCache.Keys) + { + if (key.InvariantStartsWith(keyStartsWith)) + { + ClearCacheItem(key); + } + } + } + + public override void ClearCacheByKeyExpression(string regexString) + { + foreach (var key in _staticCache.Keys) + { + if (Regex.IsMatch(key, regexString)) + { + ClearCacheItem(key); + } + } + } + + public override IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) + { + return (from KeyValuePair c in _staticCache + where c.Key.InvariantStartsWith(keyStartsWith) + select c.Value.TryConvertTo() + into attempt + where attempt.Success + select attempt.Result).ToList(); + } + + public override T GetCacheItem(string cacheKey) + { + var result = _staticCache[cacheKey]; + if (result == null) + { + return default(T); + } + return result.TryConvertTo().Result; + } + + public override T GetCacheItem(string cacheKey, Func getCacheItem) + { + return (T)_staticCache.GetOrAdd(cacheKey, getCacheItem); + } + + } +} \ No newline at end of file diff --git a/src/Umbraco.Core/CacheHelper.cs b/src/Umbraco.Core/CacheHelper.cs index 7c80a1e6d3..96b3d99de9 100644 --- a/src/Umbraco.Core/CacheHelper.cs +++ b/src/Umbraco.Core/CacheHelper.cs @@ -15,14 +15,13 @@ namespace Umbraco.Core /// /// Class that is exposed by the ApplicationContext for application wide caching purposes /// - /// - /// This class may be opened publicly at some point but needs a review of what is absoletely necessary. - /// - public class CacheHelper //: CacheProviderBase + public class CacheHelper { private readonly bool _enableCache; - private readonly HttpRuntimeCacheProvider _httpCache; - private readonly NullCacheProvider _nullCache = new NullCacheProvider(); + private readonly CacheProviderBase _staticCache; + private readonly CacheProviderBase _nullStaticCache = new NullCacheProvider(); + private readonly RuntimeCacheProviderBase _httpCache; + private readonly RuntimeCacheProviderBase _nullHttpCache = new NullCacheProvider(); public CacheHelper(System.Web.Caching.Cache cache) : this(cache, true) @@ -30,21 +29,185 @@ namespace Umbraco.Core } internal CacheHelper(System.Web.Caching.Cache cache, bool enableCache) - { - _httpCache = new HttpRuntimeCacheProvider(cache); - _enableCache = enableCache; + : this(new HttpRuntimeCacheProvider(cache), enableCache) + { } - + + internal CacheHelper(RuntimeCacheProviderBase httpCacheProvider, bool enableCache) + : this(httpCacheProvider, new StaticCacheProvider(), enableCache) + { + } + + internal CacheHelper(RuntimeCacheProviderBase httpCacheProvider, CacheProviderBase staticCacheProvider, bool enableCache) + { + _httpCache = httpCacheProvider; + _staticCache = staticCacheProvider; + _enableCache = enableCache; + } + + #region Static cache + + /// + /// Clears the item in umbraco's static cache + /// + internal void ClearAllStaticCache() + { + if (!_enableCache) + { + _nullStaticCache.ClearAllCache(); + } + else + { + _staticCache.ClearAllCache(); + } + } + + /// + /// Clears the item in umbraco's static cache with the given key + /// + /// Key + internal void ClearStaticCacheItem(string key) + { + if (!_enableCache) + { + _nullStaticCache.ClearCacheItem(key); + } + else + { + _staticCache.ClearCacheItem(key); + } + } + + /// + /// Clears all objects in the static cache with the System.Type name as the + /// input parameter. (using [object].GetType()) + /// + /// The name of the System.Type which should be cleared from cache ex "System.Xml.XmlDocument" + internal void ClearStaticCacheObjectTypes(string typeName) + { + if (!_enableCache) + { + _nullStaticCache.ClearCacheObjectTypes(typeName); + } + else + { + _staticCache.ClearCacheObjectTypes(typeName); + } + } + + /// + /// Clears all objects in the static cache with the System.Type specified + /// + internal void ClearStaticCacheObjectTypes() + { + if (!_enableCache) + { + _nullStaticCache.ClearCacheObjectTypes(); + } + else + { + _staticCache.ClearCacheObjectTypes(); + } + } + + /// + /// Clears all static cache items that starts with the key passed. + /// + /// The start of the key + internal void ClearStaticCacheByKeySearch(string keyStartsWith) + { + if (!_enableCache) + { + _nullStaticCache.ClearCacheByKeySearch(keyStartsWith); + } + else + { + _staticCache.ClearCacheByKeySearch(keyStartsWith); + } + } + + /// + /// Clears all cache items that have a key that matches the regular expression + /// + /// + internal void ClearStaticCacheByKeyExpression(string regexString) + { + if (!_enableCache) + { + _nullStaticCache.ClearCacheByKeyExpression(regexString); + } + else + { + _staticCache.ClearCacheByKeyExpression(regexString); + } + } + + internal IEnumerable GetStaticCacheItemsByKeySearch(string keyStartsWith) + { + if (!_enableCache) + { + return _nullStaticCache.GetCacheItemsByKeySearch(keyStartsWith); + } + else + { + return _staticCache.GetCacheItemsByKeySearch(keyStartsWith); + } + } + + /// + /// Returns a static cache item by key, does not update the cache if it isn't there. + /// + /// + /// + /// + internal TT GetStaticCacheItem(string cacheKey) + { + if (!_enableCache) + { + return _nullStaticCache.GetCacheItem(cacheKey); + } + else + { + return _staticCache.GetCacheItem(cacheKey); + } + } + + /// + /// Gets (and adds if necessary) an item from the static cache with all of the default parameters + /// + /// + /// + /// + /// + internal TT GetStaticCacheItem(string cacheKey, Func getCacheItem) + { + if (!_enableCache) + { + return _nullStaticCache.GetCacheItem(cacheKey, getCacheItem); + } + else + { + return _staticCache.GetCacheItem(cacheKey, getCacheItem); + } + } + + #endregion + + + #region Runtime/Http Cache + /// + /// Clears the item in umbraco's runtime cache + /// public void ClearAllCache() - { - if (!_enableCache) - { - _nullCache.ClearAllCache(); - } - else - { - _httpCache.ClearAllCache(); - } + { + if (!_enableCache) + { + _nullHttpCache.ClearAllCache(); + } + else + { + _httpCache.ClearAllCache(); + } } /// @@ -55,7 +218,7 @@ namespace Umbraco.Core { if (!_enableCache) { - _nullCache.ClearCacheItem(key); + _nullHttpCache.ClearCacheItem(key); } else { @@ -73,7 +236,7 @@ namespace Umbraco.Core { if (!_enableCache) { - _nullCache.ClearCacheObjectTypes(typeName); + _nullHttpCache.ClearCacheObjectTypes(typeName); } else { @@ -84,55 +247,55 @@ namespace Umbraco.Core /// /// Clears all objects in the System.Web.Cache with the System.Type specified /// - public void ClearCacheObjectTypes() - { + public void ClearCacheObjectTypes() + { if (!_enableCache) { - _nullCache.ClearCacheObjectTypes(); + _nullHttpCache.ClearCacheObjectTypes(); } else { _httpCache.ClearCacheObjectTypes(); } - } + } - /// + /// /// Clears all cache items that starts with the key passed. /// /// The start of the key public void ClearCacheByKeySearch(string keyStartsWith) - { + { if (!_enableCache) { - _nullCache.ClearCacheByKeySearch(keyStartsWith); + _nullHttpCache.ClearCacheByKeySearch(keyStartsWith); } else { _httpCache.ClearCacheByKeySearch(keyStartsWith); } - } + } /// /// Clears all cache items that have a key that matches the regular expression /// /// - public void ClearCacheByKeyExpression(string regexString) - { + public void ClearCacheByKeyExpression(string regexString) + { if (!_enableCache) { - _nullCache.ClearCacheByKeyExpression(regexString); + _nullHttpCache.ClearCacheByKeyExpression(regexString); } else { _httpCache.ClearCacheByKeyExpression(regexString); } - } + } public IEnumerable GetCacheItemsByKeySearch(string keyStartsWith) { if (!_enableCache) { - return _nullCache.GetCacheItemsByKeySearch(keyStartsWith); + return _nullHttpCache.GetCacheItemsByKeySearch(keyStartsWith); } else { @@ -140,7 +303,7 @@ namespace Umbraco.Core } } - /// + /// /// Returns a cache item by key, does not update the cache if it isn't there. /// /// @@ -150,7 +313,7 @@ namespace Umbraco.Core { if (!_enableCache) { - return _nullCache.GetCacheItem(cacheKey); + return _nullHttpCache.GetCacheItem(cacheKey); } else { @@ -169,7 +332,7 @@ namespace Umbraco.Core { if (!_enableCache) { - return _nullCache.GetCacheItem(cacheKey, getCacheItem); + return _nullHttpCache.GetCacheItem(cacheKey, getCacheItem); } else { @@ -190,7 +353,7 @@ namespace Umbraco.Core { if (!_enableCache) { - return _nullCache.GetCacheItem(cacheKey, timeout, getCacheItem); + return _nullHttpCache.GetCacheItem(cacheKey, timeout, getCacheItem); } else { @@ -213,7 +376,7 @@ namespace Umbraco.Core { if (!_enableCache) { - return _nullCache.GetCacheItem(cacheKey, refreshAction, timeout, getCacheItem); + return _nullHttpCache.GetCacheItem(cacheKey, refreshAction, timeout, getCacheItem); } else { @@ -237,7 +400,7 @@ namespace Umbraco.Core { if (!_enableCache) { - return _nullCache.GetCacheItem(cacheKey, priority, refreshAction, timeout, getCacheItem); + return _nullHttpCache.GetCacheItem(cacheKey, priority, refreshAction, timeout, getCacheItem); } else { @@ -257,15 +420,15 @@ namespace Umbraco.Core /// /// public TT GetCacheItem(string cacheKey, - CacheItemPriority priority, - CacheItemRemovedCallback refreshAction, - CacheDependency cacheDependency, - TimeSpan timeout, - Func getCacheItem) + CacheItemPriority priority, + CacheItemRemovedCallback refreshAction, + CacheDependency cacheDependency, + TimeSpan timeout, + Func getCacheItem) { if (!_enableCache) { - return _nullCache.GetCacheItem(cacheKey, priority, refreshAction, cacheDependency, timeout, getCacheItem); + return _nullHttpCache.GetCacheItem(cacheKey, priority, refreshAction, cacheDependency, timeout, getCacheItem); } else { @@ -289,7 +452,7 @@ namespace Umbraco.Core { if (!_enableCache) { - return _nullCache.GetCacheItem(cacheKey, priority, null, cacheDependency, null, getCacheItem); + return _nullHttpCache.GetCacheItem(cacheKey, priority, null, cacheDependency, null, getCacheItem); } else { @@ -310,14 +473,14 @@ namespace Umbraco.Core { if (!_enableCache) { - _nullCache.InsertCacheItem(cacheKey, priority, getCacheItem); + _nullHttpCache.InsertCacheItem(cacheKey, priority, getCacheItem); } else { _httpCache.InsertCacheItem(cacheKey, priority, getCacheItem); } } - + /// /// Inserts an item into the cache, if it already exists in the cache it will be replaced /// @@ -333,7 +496,7 @@ namespace Umbraco.Core { if (!_enableCache) { - _nullCache.InsertCacheItem(cacheKey, priority, timeout, getCacheItem); + _nullHttpCache.InsertCacheItem(cacheKey, priority, timeout, getCacheItem); } else { @@ -341,30 +504,30 @@ namespace Umbraco.Core } } - /// - /// Inserts an item into the cache, if it already exists in the cache it will be replaced - /// - /// - /// - /// - /// - /// This will set an absolute expiration from now until the timeout - /// - public void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - CacheDependency cacheDependency, - TimeSpan timeout, - Func getCacheItem) - { + /// + /// Inserts an item into the cache, if it already exists in the cache it will be replaced + /// + /// + /// + /// + /// + /// This will set an absolute expiration from now until the timeout + /// + public void InsertCacheItem(string cacheKey, + CacheItemPriority priority, + CacheDependency cacheDependency, + TimeSpan timeout, + Func getCacheItem) + { if (!_enableCache) { - _nullCache.InsertCacheItem(cacheKey, priority, cacheDependency, timeout, getCacheItem); + _nullHttpCache.InsertCacheItem(cacheKey, priority, cacheDependency, timeout, getCacheItem); } else { _httpCache.InsertCacheItem(cacheKey, priority, cacheDependency, timeout, getCacheItem); } - } + } /// /// Inserts an item into the cache, if it already exists in the cache it will be replaced @@ -376,22 +539,23 @@ namespace Umbraco.Core /// /// This will set an absolute expiration from now until the timeout /// - public void InsertCacheItem(string cacheKey, - CacheItemPriority priority, - CacheItemRemovedCallback refreshAction, - CacheDependency cacheDependency, - TimeSpan? timeout, - Func getCacheItem) - { + public void InsertCacheItem(string cacheKey, + CacheItemPriority priority, + CacheItemRemovedCallback refreshAction, + CacheDependency cacheDependency, + TimeSpan? timeout, + Func getCacheItem) + { if (!_enableCache) { - _nullCache.InsertCacheItem(cacheKey, priority, refreshAction, cacheDependency, timeout, getCacheItem); + _nullHttpCache.InsertCacheItem(cacheKey, priority, refreshAction, cacheDependency, timeout, getCacheItem); } else { _httpCache.InsertCacheItem(cacheKey, priority, refreshAction, cacheDependency, timeout, getCacheItem); } - } + } + #endregion } diff --git a/src/Umbraco.Core/DictionaryExtensions.cs b/src/Umbraco.Core/DictionaryExtensions.cs index 83d89fcaba..73866f2c9b 100644 --- a/src/Umbraco.Core/DictionaryExtensions.cs +++ b/src/Umbraco.Core/DictionaryExtensions.cs @@ -1,5 +1,6 @@ using System; using System.Collections; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; @@ -10,10 +11,60 @@ using System.Web; namespace Umbraco.Core { /// - /// Extension methods for dictionary + /// Extension methods for dictionary & concurrentdictionary /// internal static class DictionaryExtensions { + /// + /// Updates an item with the specified key with the specified value + /// + /// + /// + /// + /// + /// + /// + /// + /// Taken from: http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression + /// + /// If there is an item in the dictionary with the key, it will keep trying to update it until it can + /// + public static bool TryUpdate(this ConcurrentDictionary dict, TKey key, Func updateFactory) + { + TValue curValue; + while (dict.TryGetValue(key, out curValue)) + { + if (dict.TryUpdate(key, updateFactory(curValue), curValue)) + return true; + //if we're looping either the key was removed by another thread, or another thread + //changed the value, so we start again. + } + return false; + } + + /// + /// Updates an item with the specified key with the specified value + /// + /// + /// + /// + /// + /// + /// + /// + /// Taken from: http://stackoverflow.com/questions/12240219/is-there-a-way-to-use-concurrentdictionary-tryupdate-with-a-lambda-expression + /// + /// WARNING: If the value changes after we've retreived it, then the item will not be updated + /// + public static bool TryUpdateOptimisitic(this ConcurrentDictionary dict, TKey key, Func updateFactory) + { + TValue curValue; + if (!dict.TryGetValue(key, out curValue)) + return false; + dict.TryUpdate(key, updateFactory(curValue), curValue); + return true;//note we return true whether we succeed or not, see explanation below. + } + /// /// Converts a dictionary to another type by only using direct casting /// diff --git a/src/Umbraco.Core/Umbraco.Core.csproj b/src/Umbraco.Core/Umbraco.Core.csproj index 3279f12251..6f23a9f69e 100644 --- a/src/Umbraco.Core/Umbraco.Core.csproj +++ b/src/Umbraco.Core/Umbraco.Core.csproj @@ -112,13 +112,15 @@ + - + +