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.

This commit is contained in:
Shannon
2013-07-23 11:46:18 +10:00
parent 3da7682415
commit 8be54ba0cc
4 changed files with 96 additions and 17 deletions

View File

@@ -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
/// <summary>
/// The Runtime Cache provider looks up objects in the Runtime cache for fast retrival
/// </summary>
/// <remarks>
///
/// 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.
///
/// </remarks>
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<string> _keyTracker = new ConcurrentHashSet<string>();
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<string>();
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);
}
}
}
}
}
/// <summary>
/// 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.
/// </summary>
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());
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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));
}
}