Merge pull request #7306 from umbraco/v8/bugfix/AB3323-SqlMainDom

Introduce a new IMainDomLock and both default and sql implementations
This commit is contained in:
Bjarke Berg
2020-01-23 12:44:11 +01:00
committed by GitHub
24 changed files with 1356 additions and 734 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -4,6 +4,7 @@ using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Threading;
using CSharpTest.Net.Collections;
using Newtonsoft.Json;
using Umbraco.Core;
@@ -144,7 +145,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
_domainStore = new SnapDictionary<int, Domain>();
LoadCachesOnStartup();
LoadCachesOnStartup();
}
Guid GetUid(ContentStore store, int id) => store.LiveSnapshot.Get(id)?.Uid ?? default;
@@ -165,14 +166,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
/// to not run if MainDom wasn't acquired.
/// If MainDom was not acquired, then _localContentDb and _localMediaDb will remain null which means this appdomain
/// will load in published content via the DB and in that case this appdomain will probably not exist long enough to
/// serve more than a page of content.
/// serve more than a page of content.
/// </remarks>
private void MainDomRegister()
{
var path = GetLocalFilesPath();
var localContentDbPath = Path.Combine(path, "NuCache.Content.db");
var localMediaDbPath = Path.Combine(path, "NuCache.Media.db");
_localContentDbExists = File.Exists(localContentDbPath);
_localMediaDbExists = File.Exists(localMediaDbPath);
@@ -191,10 +192,15 @@ namespace Umbraco.Web.PublishedCache.NuCache
/// </remarks>
private void MainDomRelease()
{
_logger.Debug<PublishedSnapshotService>("Releasing from MainDom...");
lock (_storesLock)
{
_logger.Debug<PublishedSnapshotService>("Releasing content store...");
_contentStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned
_localContentDb = null;
_logger.Debug<PublishedSnapshotService>("Releasing media store...");
_mediaStore?.ReleaseLocalDb(); //null check because we could shut down before being assigned
_localMediaDb = null;
@@ -217,7 +223,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
okContent = LockAndLoadContent(scope => LoadContentFromLocalDbLocked(true));
if (!okContent)
_logger.Warn<PublishedSnapshotService>("Loading content from local db raised warnings, will reload from database.");
_logger.Warn<PublishedSnapshotService>("Loading content from local db raised warnings, will reload from database.");
}
if (_localMediaDbExists)
@@ -378,7 +384,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
var contentTypes = _serviceContext.ContentTypeService.GetAll()
.Select(x => _publishedContentTypeFactory.CreateContentType(x));
_contentStore.SetAllContentTypes(contentTypes);
_contentStore.SetAllContentTypesLocked(contentTypes);
using (_logger.TraceDuration<PublishedSnapshotService>("Loading content from database"))
{
@@ -389,7 +395,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// IMPORTANT GetAllContentSources sorts kits by level + parentId + sortOrder
var kits = _dataSource.GetAllContentSources(scope);
return onStartup ? _contentStore.SetAllFastSorted(kits, true) : _contentStore.SetAll(kits);
return onStartup ? _contentStore.SetAllFastSortedLocked(kits, true) : _contentStore.SetAllLocked(kits);
}
}
@@ -397,7 +403,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
var contentTypes = _serviceContext.ContentTypeService.GetAll()
.Select(x => _publishedContentTypeFactory.CreateContentType(x));
_contentStore.SetAllContentTypes(contentTypes);
_contentStore.SetAllContentTypesLocked(contentTypes);
using (_logger.TraceDuration<PublishedSnapshotService>("Loading content from local cache file"))
{
@@ -449,7 +455,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
var mediaTypes = _serviceContext.MediaTypeService.GetAll()
.Select(x => _publishedContentTypeFactory.CreateContentType(x));
_mediaStore.SetAllContentTypes(mediaTypes);
_mediaStore.SetAllContentTypesLocked(mediaTypes);
using (_logger.TraceDuration<PublishedSnapshotService>("Loading media from database"))
{
@@ -461,7 +467,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
_logger.Debug<PublishedSnapshotService>("Loading media from database...");
// IMPORTANT GetAllMediaSources sorts kits by level + parentId + sortOrder
var kits = _dataSource.GetAllMediaSources(scope);
return onStartup ? _mediaStore.SetAllFastSorted(kits, true) : _mediaStore.SetAll(kits);
return onStartup ? _mediaStore.SetAllFastSortedLocked(kits, true) : _mediaStore.SetAllLocked(kits);
}
}
@@ -469,7 +475,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
var mediaTypes = _serviceContext.MediaTypeService.GetAll()
.Select(x => _publishedContentTypeFactory.CreateContentType(x));
_mediaStore.SetAllContentTypes(mediaTypes);
_mediaStore.SetAllContentTypesLocked(mediaTypes);
using (_logger.TraceDuration<PublishedSnapshotService>("Loading media from local cache file"))
{
@@ -510,7 +516,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
return false;
}
return onStartup ? store.SetAllFastSorted(kits, false) : store.SetAll(kits);
return onStartup ? store.SetAllFastSortedLocked(kits, false) : store.SetAllLocked(kits);
}
// keep these around - might be useful
@@ -616,7 +622,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
.Where(x => x.RootContentId.HasValue && x.LanguageIsoCode.IsNullOrWhiteSpace() == false)
.Select(x => new Domain(x.Id, x.DomainName, x.RootContentId.Value, CultureInfo.GetCultureInfo(x.LanguageIsoCode), x.IsWildcard)))
{
_domainStore.Set(domain.Id, domain);
_domainStore.SetLocked(domain.Id, domain);
}
}
@@ -664,10 +670,12 @@ namespace Umbraco.Web.PublishedCache.NuCache
publishedChanged = publishedChanged2;
}
if (draftChanged || publishedChanged)
((PublishedSnapshot)CurrentPublishedSnapshot)?.Resync();
}
// Calling this method means we have a lock on the contentStore (i.e. GetScopedWriteLock)
private void NotifyLocked(IEnumerable<ContentCacheRefresher.JsonPayload> payloads, out bool draftChanged, out bool publishedChanged)
{
publishedChanged = false;
@@ -677,7 +685,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// content (and content types) are read-locked while reading content
// contentStore is wlocked (so readable, only no new views)
// and it can be wlocked by 1 thread only at a time
// contentStore is write-locked during changes
// contentStore is write-locked during changes - see note above, calls to this method are wrapped in contentStore.GetScopedWriteLock
foreach (var payload in payloads)
{
@@ -697,7 +705,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
{
if (_contentStore.Clear(payload.Id))
if (_contentStore.ClearLocked(payload.Id))
draftChanged = publishedChanged = true;
continue;
}
@@ -720,7 +728,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// ?? should we do some RV check here?
// IMPORTANT GetbranchContentSources sorts kits by level and by sort order
var kits = _dataSource.GetBranchContentSources(scope, capture.Id);
_contentStore.SetBranch(capture.Id, kits);
_contentStore.SetBranchLocked(capture.Id, kits);
}
else
{
@@ -728,11 +736,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
var kit = _dataSource.GetContentSource(scope, capture.Id);
if (kit.IsEmpty)
{
_contentStore.Clear(capture.Id);
_contentStore.ClearLocked(capture.Id);
}
else
{
_contentStore.Set(kit);
_contentStore.SetLocked(kit);
}
}
@@ -790,7 +798,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (payload.ChangeTypes.HasType(TreeChangeTypes.Remove))
{
if (_mediaStore.Clear(payload.Id))
if (_mediaStore.ClearLocked(payload.Id))
anythingChanged = true;
continue;
}
@@ -813,7 +821,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// ?? should we do some RV check here?
// IMPORTANT GetbranchContentSources sorts kits by level and by sort order
var kits = _dataSource.GetBranchMediaSources(scope, capture.Id);
_mediaStore.SetBranch(capture.Id, kits);
_mediaStore.SetBranchLocked(capture.Id, kits);
}
else
{
@@ -821,11 +829,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
var kit = _dataSource.GetMediaSource(scope, capture.Id);
if (kit.IsEmpty)
{
_mediaStore.Clear(capture.Id);
_mediaStore.ClearLocked(capture.Id);
}
else
{
_mediaStore.Set(kit);
_mediaStore.SetLocked(kit);
}
}
@@ -927,14 +935,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
using (var scope = _scopeProvider.CreateScope())
{
scope.ReadLock(Constants.Locks.ContentTree);
_contentStore.UpdateDataTypes(idsA, id => CreateContentType(PublishedItemType.Content, id));
_contentStore.UpdateDataTypesLocked(idsA, id => CreateContentType(PublishedItemType.Content, id));
scope.Complete();
}
using (var scope = _scopeProvider.CreateScope())
{
scope.ReadLock(Constants.Locks.MediaTree);
_mediaStore.UpdateDataTypes(idsA, id => CreateContentType(PublishedItemType.Media, id));
_mediaStore.UpdateDataTypesLocked(idsA, id => CreateContentType(PublishedItemType.Media, id));
scope.Complete();
}
}
@@ -964,7 +972,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
break;
case DomainChangeTypes.Remove:
_domainStore.Clear(payload.Id);
_domainStore.ClearLocked(payload.Id);
break;
case DomainChangeTypes.Refresh:
var domain = _serviceContext.DomainService.GetById(payload.Id);
@@ -972,14 +980,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (domain.RootContentId.HasValue == false) continue; // anomaly
if (domain.LanguageIsoCode.IsNullOrWhiteSpace()) continue; // anomaly
var culture = CultureInfo.GetCultureInfo(domain.LanguageIsoCode);
_domainStore.Set(domain.Id, new Domain(domain.Id, domain.DomainName, domain.RootContentId.Value, culture, domain.IsWildcard));
_domainStore.SetLocked(domain.Id, new Domain(domain.Id, domain.DomainName, domain.RootContentId.Value, culture, domain.IsWildcard));
break;
}
}
}
}
//Methods used to prevent allocations of lists
//Methods used to prevent allocations of lists
private void AddToList(ref List<int> list, int val) => GetOrCreateList(ref list).Add(val);
private List<int> GetOrCreateList(ref List<int> list) => list ?? (list = new List<int>());
@@ -1057,11 +1065,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
? Array.Empty<ContentNodeKit>()
: _dataSource.GetTypeContentSources(scope, refreshedIds).ToArray();
_contentStore.UpdateContentTypes(removedIds, typesA, kits);
_contentStore.UpdateContentTypesLocked(removedIds, typesA, kits);
if (!otherIds.IsCollectionEmpty())
_contentStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray()));
_contentStore.UpdateContentTypesLocked(CreateContentTypes(PublishedItemType.Content, otherIds.ToArray()));
if (!newIds.IsCollectionEmpty())
_contentStore.NewContentTypes(CreateContentTypes(PublishedItemType.Content, newIds.ToArray()));
_contentStore.NewContentTypesLocked(CreateContentTypes(PublishedItemType.Content, newIds.ToArray()));
scope.Complete();
}
}
@@ -1088,11 +1096,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
? Array.Empty<ContentNodeKit>()
: _dataSource.GetTypeMediaSources(scope, refreshedIds).ToArray();
_mediaStore.UpdateContentTypes(removedIds, typesA, kits);
_mediaStore.UpdateContentTypesLocked(removedIds, typesA, kits);
if (!otherIds.IsCollectionEmpty())
_mediaStore.UpdateContentTypes(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray());
_mediaStore.UpdateContentTypesLocked(CreateContentTypes(PublishedItemType.Media, otherIds.ToArray()).ToArray());
if (!newIds.IsCollectionEmpty())
_mediaStore.NewContentTypes(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray());
_mediaStore.NewContentTypesLocked(CreateContentTypes(PublishedItemType.Media, newIds.ToArray()).ToArray());
scope.Complete();
}
}
@@ -1163,12 +1171,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
// elements
// just need to make sure nothing gets elements in another enlisted action... so using
// a MaxValue to make sure this one runs last, and it should be ok
scopeContext.Enlist("Umbraco.Web.PublishedCache.NuCache.PublishedSnapshotService.Resync", () => this, (completed, svc) =>
{
((PublishedSnapshot)svc.CurrentPublishedSnapshot)?.Resync();
}, int.MaxValue);
}
// create a new snapshot cache if snapshots are different gens
if (contentSnap.Gen != _contentGen || mediaSnap.Gen != _mediaGen || domainSnap.Gen != _domainGen || _elementsCache == null)
{
@@ -1335,7 +1345,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
//culture changed on an existing language
var cultureChanged = e.SavedEntities.Any(x => !x.WasPropertyDirty(nameof(ILanguage.Id)) && x.WasPropertyDirty(nameof(ILanguage.IsoCode)));
if(cultureChanged)
if (cultureChanged)
{
RebuildContentDbCache();
}

View File

@@ -22,6 +22,11 @@ namespace Umbraco.Web.PublishedCache.NuCache
// This class is optimized for many readers, few writers
// Readers are lock-free
// NOTE - we used to lock _rlocko the long hand way with Monitor.Enter(_rlocko, ref lockTaken) but this has
// been replaced with a normal c# lock because that's exactly how the normal c# lock works,
// see https://blogs.msdn.microsoft.com/ericlippert/2009/03/06/locks-and-exceptions-do-not-mix/
// for the readlock, there's no reason here to use the long hand way.
private readonly ConcurrentDictionary<TKey, LinkedNode<TValue>> _items;
private readonly ConcurrentQueue<GenObj> _genObjs;
private GenObj _genObj;
@@ -30,7 +35,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
private long _liveGen, _floorGen;
private bool _nextGen, _collectAuto;
private Task _collectTask;
private volatile int _wlocked;
// minGenDelta to be adjusted
// we may want to throttle collects even if delta is reached
@@ -71,20 +75,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
// are all ignored - Release is private and meant to be invoked with 'commit' being false only
// only on the outermost lock (by SnapDictionaryWriter)
// using (...) {} for locking is prone to nasty leaks in case of weird exceptions
// such as thread-abort or out-of-memory, but let's not worry about it now
// side note - using (...) {} for locking is prone to nasty leaks in case of weird exceptions
// such as thread-abort or out-of-memory, which is why we've moved away from the old using wrapper we had on locking.
private readonly string _instanceId = Guid.NewGuid().ToString("N");
private class ReadLockInfo
{
public bool Taken;
}
private class WriteLockInfo
{
public bool Taken;
public bool Count;
}
// a scope contextual that represents a locked writer to the dictionary
@@ -92,8 +90,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
{
private readonly WriteLockInfo _lockinfo = new WriteLockInfo();
private readonly SnapDictionary<TKey, TValue> _dictionary;
private int _released;
public ScopedWriteLock(SnapDictionary<TKey, TValue> dictionary, bool scoped)
{
_dictionary = dictionary;
@@ -102,8 +99,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
public override void Release(bool completed)
{
if (Interlocked.CompareExchange(ref _released, 1, 0) != 0)
return;
_dictionary.Release(_lockinfo, completed);
}
}
@@ -117,28 +112,31 @@ namespace Umbraco.Web.PublishedCache.NuCache
return ScopeContextualBase.Get(scopeProvider, _instanceId, scoped => new ScopedWriteLock(this, scoped));
}
private void EnsureLocked()
{
if (!Monitor.IsEntered(_wlocko))
throw new InvalidOperationException("Write lock must be acquried.");
}
private void Lock(WriteLockInfo lockInfo, bool forceGen = false)
{
if (Monitor.IsEntered(_wlocko))
throw new InvalidOperationException("Recursive locks not allowed");
Monitor.Enter(_wlocko, ref lockInfo.Taken);
var rtaken = false;
try
lock(_rlocko)
{
Monitor.Enter(_rlocko, ref rtaken);
// assume everything in finally runs atomically
// http://stackoverflow.com/questions/18501678/can-this-unexpected-behavior-of-prepareconstrainedregions-and-thread-abort-be-ex
// http://joeduffyblog.com/2005/03/18/atomicity-and-asynchronous-exception-failures/
// http://joeduffyblog.com/2007/02/07/introducing-the-new-readerwriterlockslim-in-orcas/
// http://chabster.blogspot.fr/2013/12/readerwriterlockslim-fails-on-dual.html
//RuntimeHelpers.PrepareConstrainedRegions();
try { } finally
try { }
finally
{
// increment the lock count, and register that this lock is counting
_wlocked++;
lockInfo.Count = true;
if (_nextGen == false || (forceGen && _wlocked == 1))
if (_nextGen == false || (forceGen))
{
// because we are changing things, a new generation
// is created, which will trigger a new snapshot
@@ -149,15 +147,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
}
finally
{
if (rtaken) Monitor.Exit(_rlocko);
}
}
private void Lock(ReadLockInfo lockInfo)
{
Monitor.Enter(_rlocko, ref lockInfo.Taken);
}
private void Release(WriteLockInfo lockInfo, bool commit = true)
@@ -168,22 +157,17 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (commit == false)
{
var rtaken = false;
try
lock(_rlocko)
{
Monitor.Enter(_rlocko, ref rtaken);
try { } finally
try { }
finally
{
// forget about the temp. liveGen
_nextGen = false;
_liveGen -= 1;
}
}
finally
{
if (rtaken) Monitor.Exit(_rlocko);
}
foreach (var item in _items)
{
var link = item.Value;
@@ -197,16 +181,10 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
// decrement the lock count, if counting, then exit the lock
if (lockInfo.Count) _wlocked--;
// TODO: Shouldn't this be in a finally block?
Monitor.Exit(_wlocko);
}
private void Release(ReadLockInfo lockInfo)
{
if (lockInfo.Taken) Monitor.Exit(_rlocko);
}
#endregion
#region Set, Clear, Get, Has
@@ -219,75 +197,59 @@ namespace Umbraco.Web.PublishedCache.NuCache
return link;
}
public void Set(TKey key, TValue value)
public void SetLocked(TKey key, TValue value)
{
var lockInfo = new WriteLockInfo();
try
{
Lock(lockInfo);
EnsureLocked();
// this is safe only because we're write-locked
var link = GetHead(key);
if (link != null)
// this is safe only because we're write-locked
var link = GetHead(key);
if (link != null)
{
// already in the dict
if (link.Gen != _liveGen)
{
// already in the dict
if (link.Gen != _liveGen)
{
// for an older gen - if value is different then insert a new
// link for the new gen, with the new value
if (link.Value != value)
_items.TryUpdate(key, new LinkedNode<TValue>(value, _liveGen, link), link);
}
else
{
// for the live gen - we can fix the live gen - and remove it
// if value is null and there's no next gen
if (value == null && link.Next == null)
_items.TryRemove(key, out link);
else
link.Value = value;
}
// for an older gen - if value is different then insert a new
// link for the new gen, with the new value
if (link.Value != value)
_items.TryUpdate(key, new LinkedNode<TValue>(value, _liveGen, link), link);
}
else
{
_items.TryAdd(key, new LinkedNode<TValue>(value, _liveGen));
}
}
finally
{
Release(lockInfo);
}
}
public void Clear(TKey key)
{
Set(key, null);
}
public void Clear()
{
var lockInfo = new WriteLockInfo();
try
{
Lock(lockInfo);
// this is safe only because we're write-locked
foreach (var kvp in _items.Where(x => x.Value != null))
{
if (kvp.Value.Gen < _liveGen)
{
var link = new LinkedNode<TValue>(null, _liveGen, kvp.Value);
_items.TryUpdate(kvp.Key, link, kvp.Value);
}
// for the live gen - we can fix the live gen - and remove it
// if value is null and there's no next gen
if (value == null && link.Next == null)
_items.TryRemove(key, out link);
else
{
kvp.Value.Value = null;
}
link.Value = value;
}
}
finally
else
{
Release(lockInfo);
_items.TryAdd(key, new LinkedNode<TValue>(value, _liveGen));
}
}
public void ClearLocked(TKey key)
{
SetLocked(key, null);
}
public void ClearLocked()
{
EnsureLocked();
// this is safe only because we're write-locked
foreach (var kvp in _items.Where(x => x.Value != null))
{
if (kvp.Value.Gen < _liveGen)
{
var link = new LinkedNode<TValue>(null, _liveGen, kvp.Value);
_items.TryUpdate(kvp.Key, link, kvp.Value);
}
else
{
kvp.Value.Value = null;
}
}
}
@@ -347,11 +309,8 @@ namespace Umbraco.Web.PublishedCache.NuCache
public Snapshot CreateSnapshot()
{
var lockInfo = new ReadLockInfo();
try
lock(_rlocko)
{
Lock(lockInfo);
// if no next generation is required, and we already have a gen object,
// use it to create a new snapshot
if (_nextGen == false && _genObj != null)
@@ -360,7 +319,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// else we need to try to create a new gen object
// whether we are wlocked or not, noone can rlock while we do,
// so _liveGen and _nextGen are safe
if (_wlocked > 0) // volatile, cannot ++ but could --
if (Monitor.IsEntered(_wlocko))
{
// write-locked, cannot use latest gen (at least 1) so use previous
var snapGen = _nextGen ? _liveGen - 1 : _liveGen;
@@ -398,10 +357,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
return snapshot;
}
finally
{
Release(lockInfo);
}
}
public Task CollectAsync()
@@ -496,17 +451,18 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
public /*async*/ Task PendingCollect()
{
Task task;
lock (_rlocko)
{
task = _collectTask;
}
return task ?? Task.CompletedTask;
//if (task != null)
// await task;
}
// TODO: This is never used? Should it be? Maybe move to TestHelper below?
//public /*async*/ Task PendingCollect()
//{
// Task task;
// lock (_rlocko)
// {
// task = _collectTask;
// }
// return task ?? Task.CompletedTask;
// //if (task != null)
// // await task;
//}
public long GenCount => _genObjs.Count;
@@ -531,7 +487,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
public long LiveGen => _dict._liveGen;
public long FloorGen => _dict._floorGen;
public bool NextGen => _dict._nextGen;
public int WLocked => _dict._wlocked;
public bool IsLocked => Monitor.IsEntered(_dict._wlocko);
public bool CollectAuto
{

View File

@@ -1,10 +1,6 @@
NuCache Documentation
======================
NOTE - RENAMING
Facade = PublishedSnapshot
and everything needs to be renamed accordingly
HOW IT WORKS
-------------
@@ -22,12 +18,12 @@ When reading the cache, we read views up the chain until we find a value (which
null) for the given id, and finally we read the store itself.
The FacadeService manages a ContentStore for content, and another for media.
When a Facade is created, the FacadeService gets ContentView objects from the stores.
The PublishedSnapshotService manages a ContentStore for content, and another for media.
When a PublishedSnapshot is created, the PublishedSnapshotService gets ContentView objects from the stores.
Views provide an immutable snapshot of the content and media.
When the FacadeService is notified of changes, it notifies the stores.
Then it resync the current Facade, so that it requires new views, etc.
When the PublishedSnapshotService is notified of changes, it notifies the stores.
Then it resync the current PublishedSnapshot, so that it requires new views, etc.
Whenever a content, media or member is modified or removed, the cmsContentNu table
is updated with a json dictionary of alias => property value, so that a content,
@@ -50,32 +46,32 @@ Each ContentStore has a _freezeLock object used to protect the 'Frozen'
state of the store. It's a disposable object that releases the lock when disposed,
so usage would be: using (store.Frozen) { ... }.
The FacadeService has a _storesLock object used to guarantee atomic access to the
The PublishedSnapshotService has a _storesLock object used to guarantee atomic access to the
set of content, media stores.
CACHE
------
For each set of views, the FacadeService creates a SnapshotCache. So a SnapshotCache
For each set of views, the PublishedSnapshotService creates a SnapshotCache. So a SnapshotCache
is valid until anything changes in the content or media trees. In other words, things
that go in the SnapshotCache stay until a content or media is modified.
For each Facade, the FacadeService creates a FacadeCache. So a FacadeCache is valid
for the duration of the Facade (usually, the request). In other words, things that go
in the FacadeCache stay (and are visible to) for the duration of the request only.
For each PublishedSnapshot, the PublishedSnapshotService creates a PublishedSnapshotCache. So a PublishedSnapshotCache is valid
for the duration of the PublishedSnapshot (usually, the request). In other words, things that go
in the PublishedSnapshotCache stay (and are visible to) for the duration of the request only.
The FacadeService defines a static constant FullCacheWhenPreviewing, that defines
The PublishedSnapshotService defines a static constant FullCacheWhenPreviewing, that defines
how caches operate when previewing:
- when true, the caches in preview mode work normally.
- when false, everything that would go to the SnapshotCache goes to the FacadeCache.
- when false, everything that would go to the SnapshotCache goes to the PublishedSnapshotCache.
At the moment it is true in the code, which means that eg converted values for
previewed content will go in the SnapshotCache. Makes for faster preview, but uses
more memory on the long term... would need some benchmarking to figure out what is
best.
Members only live for the duration of the Facade. So, for members SnapshotCache is
never used, and everything goes to the FacadeCache.
Members only live for the duration of the PublishedSnapshot. So, for members SnapshotCache is
never used, and everything goes to the PublishedSnapshotCache.
All cache keys are computed in the CacheKeys static class.
@@ -85,15 +81,15 @@ TESTS
For testing purposes the following mechanisms exist:
The Facade type has a static Current property that is used to obtain the 'current'
facade in many places, going through the PublishedCachesServiceResolver to get the
current service, and asking the current service for the current facade, which by
The PublishedSnapshot type has a static Current property that is used to obtain the 'current'
PublishedSnapshot in many places, going through the PublishedCachesServiceResolver to get the
current service, and asking the current service for the current PublishedSnapshot, which by
default relies on UmbracoContext. For test purposes, it is possible to override the
entire mechanism by defining Facade.GetCurrentFacadeFunc which should return a facade.
entire mechanism by defining PublishedSnapshot.GetCurrentPublishedSnapshotFunc which should return a PublishedSnapshot.
A PublishedContent keeps only id-references to its parent and children, and needs a
way to retrieve the actual objects from the cache - which depends on the current
facade. It is possible to override the entire mechanism by defining PublishedContent.
PublishedSnapshot. It is possible to override the entire mechanism by defining PublishedContent.
GetContentByIdFunc or .GetMediaByIdFunc.
Setting these functions must be done before Resolution is frozen.
@@ -110,7 +106,7 @@ possible to support detached contents & properties, even those that do not have
int id, but again this should be refactored entirely anyway.
Not doing any row-version checks (see XmlStore) when reloading from database, though it
is maintained in the database. Two FIXME in FacadeService. Should we do it?
is maintained in the database. Two FIXME in PublishedSnapshotService. Should we do it?
There is no on-disk cache at all so everything is reloaded from the cmsContentNu table
when the site restarts. This is pretty fast, but we should experiment with solutions to
@@ -121,4 +117,4 @@ PublishedMember exposes properties that IPublishedContent does not, and that are
to be lost soon as the member is wrapped (content set, model...) - so we probably need
some sort of IPublishedMember.
/
/