diff --git a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs index e79b61ed5d..6a53e8bc5c 100644 --- a/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs +++ b/src/Umbraco.Core/Persistence/Caching/RuntimeCacheProvider.cs @@ -1,9 +1,11 @@ using System; +using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; using System.Runtime.Caching; using System.Threading; +using System.Web; using Umbraco.Core.Models.EntityBase; namespace Umbraco.Core.Persistence.Caching @@ -11,6 +13,19 @@ namespace Umbraco.Core.Persistence.Caching /// /// The Runtime Cache provider looks up objects in the Runtime cache for fast retrival /// + /// + /// + /// If a web session is detected then the HttpRuntime.Cache will be used for the runtime cache, otherwise a custom + /// MemoryCache instance will be used. It is important to use the HttpRuntime.Cache when a web session is detected so + /// that the memory management of cache in IIS can be handled appopriately. + /// + /// When a web sessions is detected we will pre-fix all HttpRuntime.Cache entries so that when we clear it we are only + /// clearing items that have been inserted by this provider. + /// + /// NOTE: These changes are all temporary until we finalize the ApplicationCache implementation which will support static cache, runtime cache + /// and request based cache which will all live in one central location so it is easily managed. + /// + /// internal sealed class RuntimeCacheProvider : IRepositoryCacheProvider { #region Singleton @@ -21,19 +36,25 @@ namespace Umbraco.Core.Persistence.Caching private RuntimeCacheProvider() { + if (HttpContext.Current == null) + { + _memoryCache = new MemoryCache("in-memory"); + } } #endregion //TODO Save this in cache as well, so its not limited to a single server usage private readonly ConcurrentHashSet _keyTracker = new ConcurrentHashSet(); - private ObjectCache _memoryCache = new MemoryCache("in-memory"); + private ObjectCache _memoryCache; private static readonly ReaderWriterLockSlim ClearLock = new ReaderWriterLockSlim(); public IEntity GetById(Type type, Guid id) { var key = GetCompositeId(type, id); - var item = _memoryCache.Get(key); + var item = HttpContext.Current == null + ? _memoryCache.Get(key) + : HttpRuntime.Cache.Get(key); return item as IEntity; } @@ -41,7 +62,11 @@ namespace Umbraco.Core.Persistence.Caching { foreach (var guid in ids) { - yield return _memoryCache.Get(GetCompositeId(type, guid)) as IEntity; + var item = HttpContext.Current == null + ? _memoryCache.Get(GetCompositeId(type, guid)) + : HttpRuntime.Cache.Get(GetCompositeId(type, guid)); + + yield return item as IEntity; } } @@ -51,7 +76,11 @@ namespace Umbraco.Core.Persistence.Caching { if (key.StartsWith(type.Name)) { - yield return _memoryCache.Get(key) as IEntity; + var item = HttpContext.Current == null + ? _memoryCache.Get(key) + : HttpRuntime.Cache.Get(key); + + yield return item as IEntity; } } } @@ -59,21 +88,34 @@ namespace Umbraco.Core.Persistence.Caching public void Save(Type type, IEntity entity) { var key = GetCompositeId(type, entity.Id); - var exists = _memoryCache.GetCacheItem(key) != null; + _keyTracker.TryAdd(key); - if (exists) + + //NOTE: Before we were checking if it already exists but the MemoryCache.Set handles this implicitly and does + // an add or update, same goes for HttpRuntime.Cache.Insert. + + if (HttpContext.Current == null) { _memoryCache.Set(key, entity, new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(5) }); - return; } - - _memoryCache.Add(key, entity, new CacheItemPolicy { SlidingExpiration = TimeSpan.FromMinutes(5) }); + else + { + HttpRuntime.Cache.Insert(key, entity, null, System.Web.Caching.Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(5)); + } } public void Delete(Type type, IEntity entity) { var key = GetCompositeId(type, entity.Id); - _memoryCache.Remove(key); + if (HttpContext.Current == null) + { + _memoryCache.Remove(key); + } + else + { + HttpRuntime.Cache.Remove(key); + } + _keyTracker.Remove(key); } @@ -88,14 +130,21 @@ namespace Umbraco.Core.Persistence.Caching var keys = new string[_keyTracker.Count]; _keyTracker.CopyTo(keys, 0); var keysToRemove = new List(); - foreach (var key in keys.Where(x => x.StartsWith(string.Format("{0}-", type.Name)))) + foreach (var key in keys.Where(x => x.StartsWith(string.Format("{0}{1}-", CacheItemPrefix, type.Name)))) { _keyTracker.Remove(key); keysToRemove.Add(key); } foreach (var key in keysToRemove) { - _memoryCache.Remove(key); + if (HttpContext.Current == null) + { + _memoryCache.Remove(key); + } + else + { + HttpRuntime.Cache.Remove(key); + } } } } @@ -105,19 +154,40 @@ namespace Umbraco.Core.Persistence.Caching using (new WriteLock(ClearLock)) { _keyTracker.Clear(); - _memoryCache.DisposeIfDisposable(); - _memoryCache = new MemoryCache("in-memory"); + + if (HttpContext.Current == null) + { + _memoryCache.DisposeIfDisposable(); + _memoryCache = new MemoryCache("in-memory"); + } + else + { + foreach (DictionaryEntry c in HttpRuntime.Cache) + { + if (c.Key is string && ((string)c.Key).InvariantStartsWith(CacheItemPrefix)) + { + if (HttpRuntime.Cache[(string)c.Key] == null) return; + HttpRuntime.Cache.Remove((string)c.Key); + } + } + } } } + /// + /// We prefix all cache keys with this so that we know which ones this class has created when + /// using the HttpRuntime cache so that when we clear it we don't clear other entries we didn't create. + /// + private const string CacheItemPrefix = "umbrtmche_"; + private string GetCompositeId(Type type, Guid id) { - return string.Format("{0}-{1}", type.Name, id.ToString()); + return string.Format("{0}{1}-{2}", CacheItemPrefix, type.Name, id.ToString()); } private string GetCompositeId(Type type, int id) { - return string.Format("{0}-{1}", type.Name, id.ToGuid()); + return string.Format("{0}{1}-{2}", CacheItemPrefix, type.Name, id.ToGuid()); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Persistence/RepositoryFactory.cs b/src/Umbraco.Core/Persistence/RepositoryFactory.cs index 73c7b290f9..d8d4b1c7e2 100644 --- a/src/Umbraco.Core/Persistence/RepositoryFactory.cs +++ b/src/Umbraco.Core/Persistence/RepositoryFactory.cs @@ -118,7 +118,7 @@ namespace Umbraco.Core.Persistence public virtual ITemplateRepository CreateTemplateRepository(IDatabaseUnitOfWork uow) { - return new TemplateRepository(uow, NullCacheProvider.Current); + return new TemplateRepository(uow, RuntimeCacheProvider.Current); } } } \ No newline at end of file diff --git a/src/Umbraco.Core/Services/ContentService.cs b/src/Umbraco.Core/Services/ContentService.cs index b3f8c9dec3..ed47bc76d6 100644 --- a/src/Umbraco.Core/Services/ContentService.cs +++ b/src/Umbraco.Core/Services/ContentService.cs @@ -1122,6 +1122,9 @@ namespace Umbraco.Core.Services Audit.Add(AuditTypes.Copy, "Copy Content performed by user", content.WriterId, content.Id); + + //TODO: Don't think we need this here because cache should be cleared by the event listeners + // and the correct ICacheRefreshers!? RuntimeCacheProvider.Current.Clear(); return copy; diff --git a/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs b/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs index 0aec83104a..42026a7523 100644 --- a/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs +++ b/src/Umbraco.Web/Cache/TemplateCacheRefresher.cs @@ -1,6 +1,8 @@ using System; using Umbraco.Core; using Umbraco.Core.Cache; +using Umbraco.Core.Models; +using Umbraco.Core.Persistence.Caching; using umbraco; using umbraco.interfaces; @@ -55,6 +57,10 @@ namespace Umbraco.Web.Cache ApplicationContext.Current.ApplicationCache.ClearCacheItem( string.Format("{0}{1}", CacheKeys.TemplateBusinessLogicCacheKey, id)); + + //need to clear the runtime cache for template instances + //NOTE: This is temp until we implement the correct ApplicationCache and then we can remove the RuntimeCache, etc... + RuntimeCacheProvider.Current.Clear(typeof(ITemplate)); } }