NuCache: children as linked lists

This commit is contained in:
Stephan
2019-04-22 17:51:07 +02:00
parent 2270188668
commit 05c85bbce5
5 changed files with 227 additions and 187 deletions

View File

@@ -23,7 +23,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
private readonly IVariationContextAccessor _variationContextAccessor;
private readonly IUmbracoContextAccessor _umbracoContextAccessor;
private readonly ConcurrentDictionary<int, LinkedNode<ContentNode>> _contentNodes;
private readonly ConcurrentDictionary<int, LinkedNode<object>> _contentRootNodes;
private LinkedNode<ContentNode> _root;
private readonly ConcurrentDictionary<int, LinkedNode<IPublishedContentType>> _contentTypesById;
private readonly ConcurrentDictionary<string, LinkedNode<IPublishedContentType>> _contentTypesByAlias;
private readonly ConcurrentDictionary<Guid, int> _xmap;
@@ -60,7 +60,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
_localDb = localDb;
_contentNodes = new ConcurrentDictionary<int, LinkedNode<ContentNode>>();
_contentRootNodes = new ConcurrentDictionary<int, LinkedNode<object>>();
_root = new LinkedNode<ContentNode>(new ContentNode(), 0);
_contentTypesById = new ConcurrentDictionary<int, LinkedNode<IPublishedContentType>>();
_contentTypesByAlias = new ConcurrentDictionary<string, LinkedNode<IPublishedContentType>>(StringComparer.InvariantCultureIgnoreCase);
_xmap = new ConcurrentDictionary<Guid, int>();
@@ -176,7 +176,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
Rollback(_contentNodes);
Rollback(_contentRootNodes);
RollbackRoot();
Rollback(_contentTypesById);
Rollback(_contentTypesByAlias);
}
@@ -202,6 +202,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (lockInfo.Taken) Monitor.Exit(_rlocko);
}
private void RollbackRoot()
{
if (_root.Gen <= _liveGen) return;
if (_root.Next != null)
_root = _root.Next;
}
private void Rollback<TKey, TValue>(ConcurrentDictionary<TKey, LinkedNode<TValue>> dictionary)
where TValue : class
{
@@ -289,7 +297,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (node == null) continue;
var contentTypeId = node.ContentType.Id;
if (index.TryGetValue(contentTypeId, out var contentType) == false) continue;
SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType, _publishedSnapshotAccessor, _variationContextAccessor, _umbracoContextAccessor));
SetValueLocked(_contentNodes, node.Id, new ContentNode(node, contentType));
}
}
finally
@@ -347,7 +355,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
// perform update of content with refreshed content type - from the kits
// skip missing type, skip missing parents & unbuildable kits - what else could we do?
// skip missing type, skip missing parents & un-buildable kits - what else could we do?
// kits are ordered by level, so ParentExits is ok here
var visited = new List<int>();
foreach (var kit in kits.Where(x =>
@@ -358,7 +366,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// replacing the node: must preserve the parents
var node = GetHead(_contentNodes, kit.Node.Id)?.Value;
if (node != null)
kit.Node.ChildContentIds = node.ChildContentIds;
kit.Node.FirstChildContentId = node.FirstChildContentId;
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
@@ -412,10 +420,10 @@ namespace Umbraco.Web.PublishedCache.NuCache
foreach (var id in contentTypeNodes[contentType.Id])
{
_contentNodes.TryGetValue(id, out LinkedNode<ContentNode> link);
_contentNodes.TryGetValue(id, out var link);
if (link?.Value == null)
continue;
var node = new ContentNode(link.Value, contentType, _publishedSnapshotAccessor, _variationContextAccessor, _umbracoContextAccessor);
var node = new ContentNode(link.Value, contentType);
SetValueLocked(_contentNodes, id, node);
if (_localDb != null) RegisterChange(id, node.ToKit());
}
@@ -455,7 +463,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
private static LinkedNode<TValue> GetHead<TKey, TValue>(ConcurrentDictionary<TKey, LinkedNode<TValue>> dict, TKey key)
where TValue : class
{
dict.TryGetValue(key, out LinkedNode<TValue> link); // else null
dict.TryGetValue(key, out var link); // else null
return link;
}
@@ -464,7 +472,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// ReSharper disable LocalizableElement
if (kit.IsEmpty)
throw new ArgumentException("Kit is empty.", nameof(kit));
if (kit.Node.ChildContentIds.Count > 0)
if (kit.Node.FirstChildContentId > 0)
throw new ArgumentException("Kit content cannot have children.", nameof(kit));
// ReSharper restore LocalizableElement
@@ -476,7 +484,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
Lock(lockInfo);
// get existing
_contentNodes.TryGetValue(kit.Node.Id, out LinkedNode<ContentNode> link);
_contentNodes.TryGetValue(kit.Node.Id, out var link);
var existing = link?.Value;
// else ignore, what else could we do?
@@ -488,7 +496,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
// manage children
if (existing != null)
kit.Node.ChildContentIds = existing.ChildContentIds;
kit.Node.FirstChildContentId = existing.FirstChildContentId;
// set
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
@@ -498,13 +506,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
if (existing == null)
{
// new, add to parent
AddToParentLocked(kit.Node);
AddNodeLocked(kit.Node);
}
else if (moving)
{
// moved, remove existing from its parent, add content to its parent
RemoveFromParentLocked(existing);
AddToParentLocked(kit.Node);
RemoveNodeLocked(existing);
AddNodeLocked(kit.Node);
}
_xmap[kit.Node.Uid] = kit.Node.Id;
@@ -515,6 +523,14 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
}
private void ClearRootLocked()
{
if (_root.Gen < _liveGen)
_root = new LinkedNode<ContentNode>(new ContentNode(), _liveGen, _root);
else
_root.Value.FirstChildContentId = -1;
}
// IMPORTANT kits must be sorted out by LEVEL
public void SetAll(IEnumerable<ContentNodeKit> kits)
{
@@ -524,18 +540,18 @@ namespace Umbraco.Web.PublishedCache.NuCache
Lock(lockInfo);
ClearLocked(_contentNodes);
ClearLocked(_contentRootNodes);
ClearRootLocked();
// do NOT clear types else they are gone!
//ClearLocked(_contentTypesById);
//ClearLocked(_contentTypesByAlias);
// skip missing parents & unbuildable kits - what else could we do?
// skip missing parents & un-buildable kits - what else could we do?
foreach (var kit in kits.Where(x => ParentExistsLocked(x) && BuildKit(x)))
{
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
if (_localDb != null) RegisterChange(kit.Node.Id, kit);
AddToParentLocked(kit.Node);
AddNodeLocked(kit.Node);
_xmap[kit.Node.Uid] = kit.Node.Id;
}
@@ -555,23 +571,23 @@ namespace Umbraco.Web.PublishedCache.NuCache
Lock(lockInfo);
// get existing
_contentNodes.TryGetValue(rootContentId, out LinkedNode<ContentNode> link);
_contentNodes.TryGetValue(rootContentId, out var link);
var existing = link?.Value;
// clear
if (existing != null)
{
ClearBranchLocked(existing);
RemoveFromParentLocked(existing);
RemoveNodeLocked(existing);
}
// now add them all back
// skip missing parents & unbuildable kits - what else could we do?
// skip missing parents & un-buildable kits - what else could we do?
foreach (var kit in kits.Where(x => ParentExistsLocked(x) && BuildKit(x)))
{
SetValueLocked(_contentNodes, kit.Node.Id, kit.Node);
if (_localDb != null) RegisterChange(kit.Node.Id, kit);
AddToParentLocked(kit.Node);
AddNodeLocked(kit.Node);
_xmap[kit.Node.Uid] = kit.Node.Id;
}
@@ -601,7 +617,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
ClearBranchLocked(content);
// manage the tree
RemoveFromParentLocked(content);
RemoveNodeLocked(content);
return true;
}
@@ -626,10 +642,13 @@ namespace Umbraco.Web.PublishedCache.NuCache
_xmap.TryRemove(content.Uid, out _);
foreach (var childId in content.ChildContentIds)
var id = content.FirstChildContentId;
while (id > 0)
{
if (_contentNodes.TryGetValue(childId, out LinkedNode<ContentNode> link) == false || link.Value == null) continue;
if (!_contentNodes.TryGetValue(id, out var link) || link.Value == null)
throw new Exception("panic: failed to get child");
ClearBranchLocked(link.Value);
id = link.Value.NextSiblingContentId;
}
}
@@ -641,24 +660,37 @@ namespace Umbraco.Web.PublishedCache.NuCache
return link;
}
private void RemoveFromParentLocked(ContentNode content)
private void RemoveNodeLocked(ContentNode content)
{
// remove from root content index,
// or parent's children index
LinkedNode<ContentNode> parentLink;
if (content.ParentContentId < 0)
{
SetValueLocked(_contentRootNodes, content.Id, null);
parentLink = _root;
}
else
{
// obviously parent has to exist
var link = GetParentLink(content);
var parent = link.Value;
if (link.Gen < _liveGen)
parent = parent.CloneParent(_publishedSnapshotAccessor, _umbracoContextAccessor);
parent.ChildContentIds.Remove(content.Id);
if (link.Gen < _liveGen)
SetValueLocked(_contentNodes, parent.Id, parent);
if (!_contentNodes.TryGetValue(content.ParentContentId, out parentLink) || parentLink.Value == null)
throw new Exception("panic: failed to get parent");
}
var parent = parentLink.Value;
if (parent.FirstChildContentId == content.Id)
{
parent = GenCloneLocked(parentLink);
parent.FirstChildContentId = content.NextSiblingContentId;
}
else
{
if (!_contentNodes.TryGetValue(parent.FirstChildContentId, out var link) || link.Value == null)
throw new Exception("panic: failed to get first child");
while (link.Value.NextSiblingContentId != content.Id)
if (!_contentNodes.TryGetValue(parent.NextSiblingContentId, out link) || link.Value == null)
throw new Exception("panic: failed to get next sibling");
var prevChild = GenCloneLocked(link);
prevChild.NextSiblingContentId = content.NextSiblingContentId;
}
}
@@ -679,25 +711,90 @@ namespace Umbraco.Web.PublishedCache.NuCache
return node?.PublishedModel != null;
}
private void AddToParentLocked(ContentNode content)
private ContentNode GenCloneLocked(LinkedNode<ContentNode> link)
{
// add to root content index,
// or parent's children index
var node = link.Value;
if (node != null && link.Gen < _liveGen)
{
node = new ContentNode(link.Value);
if (link == _root)
SetRootLocked(node);
else
SetValueLocked(_contentNodes, node.Id, node);
}
return node;
}
private void AddNodeLocked(ContentNode content)
{
LinkedNode<ContentNode> parentLink;
if (content.ParentContentId < 0)
{
// need an object reference... just use this...
SetValueLocked(_contentRootNodes, content.Id, this);
parentLink = _root;
}
else
{
// assume parent has been validated and exists
var link = GetParentLink(content);
var parent = link.Value;
if (link.Gen < _liveGen)
parent = parent.CloneParent(_publishedSnapshotAccessor, _umbracoContextAccessor);
parent.ChildContentIds.Add(content.Id);
if (link.Gen < _liveGen)
SetValueLocked(_contentNodes, parent.Id, parent);
if (!_contentNodes.TryGetValue(content.ParentContentId, out parentLink) || parentLink.Value == null)
throw new Exception("panic: failed to get parent");
}
var parent = parentLink.Value;
if (parent.FirstChildContentId < 0)
{
parent = GenCloneLocked(parentLink);
parent.FirstChildContentId = content.Id;
return;
}
if (!_contentNodes.TryGetValue(parent.FirstChildContentId, out var prevChildLink) || prevChildLink.Value == null)
throw new Exception("panic: failed to get first child");
var prevChild = prevChildLink.Value;
if (prevChild.SortOrder > content.SortOrder)
{
content.NextSiblingContentId = parent.FirstChildContentId;
parent = GenCloneLocked(parentLink);
parent.FirstChildContentId = content.Id;
return;
}
while (prevChild.NextSiblingContentId > 0)
{
if (!_contentNodes.TryGetValue(prevChild.NextSiblingContentId, out var link) || link.Value == null)
throw new Exception("panic: failed to get next child");
var nextChild = link.Value;
if (nextChild.SortOrder > content.SortOrder)
{
content.NextSiblingContentId = nextChild.Id;
prevChild = GenCloneLocked(prevChildLink);
prevChild.NextSiblingContentId = content.Id;
return;
}
prevChild = nextChild;
prevChildLink = link;
}
prevChild = GenCloneLocked(prevChildLink);
prevChild.NextSiblingContentId = content.Id;
}
private void SetRootLocked(ContentNode node)
{
if (_root.Gen != _liveGen)
{
_root = new LinkedNode<ContentNode>(node, _liveGen, _root);
}
else
{
_root.Value = node;
}
}
@@ -764,18 +861,24 @@ namespace Umbraco.Web.PublishedCache.NuCache
public IEnumerable<ContentNode> GetAtRoot(long gen)
{
// look ma, no lock!
foreach (var kvp in _contentRootNodes)
var z = _root;
while (z != null)
{
var link = kvp.Value;
while (link != null)
{
if (link.Gen <= gen)
break;
link = link.Next;
}
if (link?.Value != null)
yield return Get(kvp.Key, gen);
if (z.Gen <= gen)
break;
z = z.Next;
}
if (z == null)
yield break;
var id = z.Value.FirstChildContentId;
while (id > 0)
{
if (!_contentNodes.TryGetValue(id, out var link) || link == null)
throw new Exception("panic: failed to get sibling");
yield return link.Value;
id = link.Value.NextSiblingContentId;
}
}
@@ -928,7 +1031,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
return _collectTask;
// ReSharper disable InconsistentlySynchronizedField
var task = _collectTask = Task.Run(() => Collect());
var task = _collectTask = Task.Run(Collect);
_collectTask.ContinueWith(_ =>
{
lock (_rlocko)
@@ -957,11 +1060,19 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
Collect(_contentNodes);
Collect(_contentRootNodes);
CollectRoot();
Collect(_contentTypesById);
Collect(_contentTypesByAlias);
}
private void CollectRoot()
{
var link = _root;
while (link.Next != null && link.Next.Gen > _floorGen)
link = link.Next;
link.Next = null;
}
private void Collect<TKey, TValue>(ConcurrentDictionary<TKey, LinkedNode<TValue>> dict)
where TValue : class
{