From 8be54ba0cc3b71b6958fbda413dd888374902e83 Mon Sep 17 00:00:00 2001 From: Shannon Date: Tue, 23 Jul 2013 11:46:18 +1000 Subject: [PATCH] Fixes: U4-1970 v6 API - need to implement caching for templates since we're doing a db lookup on a front-end request. This is a temporary fix until we implement the ApplicationCache properly but it still works by utilizing the existing RuntimeCacheProvider singleton. I've tweaked this provider as well to ensure we use the HttpRuntime.Cache when it is available (during a web session). We don't want to have a seperate caching bucket so that IIS can manage cache memory properly. Have ensured that the template cache is cleared when templates are updated/removed. --- .../Caching/RuntimeCacheProvider.cs | 102 +++++++++++++++--- .../Persistence/RepositoryFactory.cs | 2 +- src/Umbraco.Core/Services/ContentService.cs | 3 + .../Cache/TemplateCacheRefresher.cs | 6 ++ 4 files changed, 96 insertions(+), 17 deletions(-) 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)); } }