diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs index 1e8d5ddfc9..7379277be4 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentNode.cs @@ -12,7 +12,9 @@ namespace Umbraco.Web.PublishedCache.NuCache public ContentNode() { FirstChildContentId = -1; + LastChildContentId = -1; NextSiblingContentId = -1; + PreviousSiblingContentId = -1; } // special ctor with no content data - for members @@ -58,7 +60,9 @@ namespace Umbraco.Web.PublishedCache.NuCache SortOrder = sortOrder; ParentContentId = parentContentId; FirstChildContentId = -1; + LastChildContentId = -1; NextSiblingContentId = -1; + PreviousSiblingContentId = -1; CreateDate = createDate; CreatorId = creatorId; } @@ -95,7 +99,9 @@ namespace Umbraco.Web.PublishedCache.NuCache SortOrder = origin.SortOrder; ParentContentId = origin.ParentContentId; FirstChildContentId = origin.FirstChildContentId; + LastChildContentId = origin.LastChildContentId; NextSiblingContentId = origin.NextSiblingContentId; + PreviousSiblingContentId = origin.PreviousSiblingContentId; CreateDate = origin.CreateDate; CreatorId = origin.CreatorId; @@ -119,7 +125,9 @@ namespace Umbraco.Web.PublishedCache.NuCache public readonly int SortOrder; public readonly int ParentContentId; public int FirstChildContentId; + public int LastChildContentId; public int NextSiblingContentId; + public int PreviousSiblingContentId; public readonly DateTime CreateDate; public readonly int CreatorId; diff --git a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs index b9d1e0edf7..d6631b779d 100644 --- a/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/ContentStore.cs @@ -128,7 +128,8 @@ namespace Umbraco.Web.PublishedCache.NuCache Monitor.Enter(_rlocko, ref rtaken); // see SnapDictionary - try { } finally + try { } + finally { _wlocked++; lockInfo.Count = true; @@ -277,8 +278,8 @@ namespace Umbraco.Web.PublishedCache.NuCache public void UpdateContentTypes(IEnumerable types) { - //nothing to do if this is empty, no need to lock/allocate/iterate/etc... - if (!types.Any()) return; + //nothing to do if this is empty, no need to lock/allocate/iterate/etc... + if (!types.Any()) return; var lockInfo = new WriteLockInfo(); try @@ -550,13 +551,13 @@ namespace Umbraco.Web.PublishedCache.NuCache if (existing == null) { // new, add to parent - AddNodeLocked(kit.Node, parent); + AddTreeNodeLocked(kit.Node, parent); } else if (moving || existing.SortOrder != kit.Node.SortOrder) { // moved, remove existing from its parent, add content to its parent - RemoveNodeLocked(existing); - AddNodeLocked(kit.Node); + RemoveTreeNodeLocked(existing); + AddTreeNodeLocked(kit.Node); } else { @@ -583,13 +584,16 @@ namespace Umbraco.Web.PublishedCache.NuCache } /// - /// Builds all kits without any sorting or generation checks + /// Builds all kits on startup using a fast forward only cursor /// - /// + /// + /// All kits sorted by Level + Parent Id + Sort order + /// /// /// /// This requires that the collection is sorted by Level + ParentId + Sort Order. /// This should be used only on a site startup as the first generations. + /// This CANNOT be used after startup since it bypasses all checks for Generations. /// internal bool SetAllFastSorted(IEnumerable kits) { @@ -602,11 +606,11 @@ namespace Umbraco.Web.PublishedCache.NuCache ClearLocked(_contentNodes); ClearRootLocked(); - //these are ordered by level + sort order - // The name of the game here is to populate each kit's // FirstChildContentId + // LastChildContentId // NextSiblingContentId + // PreviousSiblingContentId ContentNode prev = null; ContentNode currParent = null; @@ -620,20 +624,28 @@ namespace Umbraco.Web.PublishedCache.NuCache } if (currParent != null && currParent.Id != parentLink.Value.Id) - prev = null; //changed parent - + { + //the parent is changing so that means the prev tracked one is the last child + currParent.LastChildContentId = prev.Id; + //changed parent, reset prev + prev = null; + } + currParent = parentLink.Value; _logger.Debug($"Set {kit.Node.Id} with parent {kit.Node.ParentContentId}"); SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); //if the parent's FirstChildContentId isn't set, then it must be the current one - if (parentLink.Value.FirstChildContentId < 0) - parentLink.Value.FirstChildContentId = kit.Node.Id; + if (currParent.FirstChildContentId < 0) + currParent.FirstChildContentId = kit.Node.Id; //if there is a previous one on the same level then set it's next sibling id to the current oen if (prev != null) + { prev.NextSiblingContentId = kit.Node.Id; + kit.Node.PreviousSiblingContentId = prev.Id; + } //store the prev prev = kit.Node; @@ -675,7 +687,7 @@ namespace Umbraco.Web.PublishedCache.NuCache SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); if (_localDb != null) RegisterChange(kit.Node.Id, kit); - AddNodeLocked(kit.Node, parent); + AddTreeNodeLocked(kit.Node, parent); _xmap[kit.Node.Uid] = kit.Node.Id; } @@ -705,7 +717,7 @@ namespace Umbraco.Web.PublishedCache.NuCache if (existing != null) { ClearBranchLocked(existing); - RemoveNodeLocked(existing); + RemoveTreeNodeLocked(existing); } // now add them all back @@ -718,7 +730,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } SetValueLocked(_contentNodes, kit.Node.Id, kit.Node); if (_localDb != null) RegisterChange(kit.Node.Id, kit); - AddNodeLocked(kit.Node, parent); + AddTreeNodeLocked(kit.Node, parent); _xmap[kit.Node.Uid] = kit.Node.Id; } @@ -750,7 +762,7 @@ namespace Umbraco.Web.PublishedCache.NuCache ClearBranchLocked(content); // manage the tree - RemoveNodeLocked(content); + RemoveTreeNodeLocked(content); return true; } @@ -798,6 +810,9 @@ namespace Umbraco.Web.PublishedCache.NuCache throw new PanicException($"failed to get {description} with id={id}"); } + /// + /// Gets the parent link node, may be null or root if ParentContentId is less than 0 + /// private LinkedNode GetParentLink(ContentNode content) { if (content.ParentContentId < 0) return _root; @@ -816,7 +831,7 @@ namespace Umbraco.Web.PublishedCache.NuCache return content.ParentContentId < 0 ? _root : GetRequiredLinkedNode(content.ParentContentId, "parent"); } - private void RemoveNodeLocked(ContentNode content) + private void RemoveTreeNodeLocked(ContentNode content) { var parentLink = content.ParentContentId < 0 ? _root @@ -828,24 +843,38 @@ namespace Umbraco.Web.PublishedCache.NuCache if (parent.FirstChildContentId < 0) throw new PanicException("no children"); - // if first, clone parent + remove first child if (parent.FirstChildContentId == content.Id) { + // if first, clone parent + remove first child parent = GenCloneLocked(parentLink); parent.FirstChildContentId = content.NextSiblingContentId; } - else + + if (parent.LastChildContentId == content.Id) { - // iterate children until the previous child - var link = GetRequiredLinkedNode(parent.FirstChildContentId, "first child"); - - while (link.Value.NextSiblingContentId != content.Id) - link = GetRequiredLinkedNode(link.Value.NextSiblingContentId, "next child"); - - // clone the previous child and replace next child - var prevChild = GenCloneLocked(link); - prevChild.NextSiblingContentId = content.NextSiblingContentId; + // if last, clone parent + remove last child + parent = GenCloneLocked(parentLink); + parent.LastChildContentId = content.PreviousSiblingContentId; } + + // maintain linked list + + if (content.NextSiblingContentId > 0) + { + var nextLink = GetRequiredLinkedNode(content.NextSiblingContentId, "next sibling"); + var next = GenCloneLocked(nextLink); + next.PreviousSiblingContentId = content.PreviousSiblingContentId; + } + + if (content.PreviousSiblingContentId > 0) + { + var prevLink = GetRequiredLinkedNode(content.PreviousSiblingContentId, "previous sibling"); + var prev = GenCloneLocked(prevLink); + prev.NextSiblingContentId = content.NextSiblingContentId; + } + + content.NextSiblingContentId = -1; + content.PreviousSiblingContentId = -1; } private bool ParentPublishedLocked(ContentNodeKit kit) @@ -873,7 +902,10 @@ namespace Umbraco.Web.PublishedCache.NuCache return node; } - private void AddNodeLocked(ContentNode content, LinkedNode parentLink = null) + /// + /// Adds a node to the tree structure. + /// + private void AddTreeNodeLocked(ContentNode content, LinkedNode parentLink = null) { parentLink = parentLink ?? GetRequiredParentLink(content); @@ -884,6 +916,7 @@ namespace Umbraco.Web.PublishedCache.NuCache { parent = GenCloneLocked(parentLink); parent.FirstChildContentId = content.Id; + parent.LastChildContentId = content.Id; return; } @@ -896,12 +929,40 @@ namespace Umbraco.Web.PublishedCache.NuCache if (child.SortOrder > content.SortOrder) { content.NextSiblingContentId = parent.FirstChildContentId; + content.PreviousSiblingContentId = -1; + parent = GenCloneLocked(parentLink); parent.FirstChildContentId = content.Id; + + child = GenCloneLocked(childLink); + child.PreviousSiblingContentId = content.Id; + return; } - // else lookup position + // get parent's last child + var lastChildLink = GetRequiredLinkedNode(parent.LastChildContentId, "last child"); + var lastChild = lastChildLink.Value; + + // if last, clone parent + append as last child + if (lastChild.SortOrder <= content.SortOrder) + { + content.PreviousSiblingContentId = parent.LastChildContentId; + content.NextSiblingContentId = -1; + + parent = GenCloneLocked(parentLink); + parent.LastChildContentId = content.Id; + + lastChild = GenCloneLocked(lastChildLink); + lastChild.NextSiblingContentId = content.Id; + + return; + } + + // else it's going somewhere in the middle, + // and this is bad, perfs-wise - we only do it when moving + // inserting in linked list is slow, optimizing would require trees + // but... that should not happen very often - and not on large amount of data while (child.NextSiblingContentId > 0) { // get next child @@ -913,8 +974,14 @@ namespace Umbraco.Web.PublishedCache.NuCache if (nextChild.SortOrder > content.SortOrder) { content.NextSiblingContentId = nextChild.Id; + content.PreviousSiblingContentId = nextChild.PreviousSiblingContentId; + child = GenCloneLocked(childLink); child.NextSiblingContentId = content.Id; + + var nnext = GenCloneLocked(nextChildLink); + nnext.PreviousSiblingContentId = content.Id; + return; } @@ -922,9 +989,8 @@ namespace Umbraco.Web.PublishedCache.NuCache child = nextChild; } - // if last, clone previous + append - child = GenCloneLocked(childLink); - child.NextSiblingContentId = content.Id; + // should never get here + throw new Exception("panic: no more children."); } // replaces the root node diff --git a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs index 6f60ed86ab..15e6574b40 100755 --- a/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs +++ b/src/Umbraco.Web/PublishedCache/NuCache/PublishedSnapshotService.cs @@ -160,7 +160,7 @@ namespace Umbraco.Web.PublishedCache.NuCache _domainStore = new SnapDictionary(); - publishedModelFactory.WithSafeLiveFactory(LoadCaches); + publishedModelFactory.WithSafeLiveFactory(LoadCachesOnStartup); Guid GetUid(ContentStore store, int id) => store.LiveSnapshot.Get(id)?.Uid ?? default; int GetId(ContentStore store, Guid uid) => store.LiveSnapshot.Get(uid)?.Id ?? default; @@ -172,7 +172,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - private void LoadCaches() + private void LoadCachesOnStartup() { lock (_storesLock) { @@ -186,19 +186,19 @@ namespace Umbraco.Web.PublishedCache.NuCache { if (_localDbExists) { - okContent = LockAndLoadContent(LoadContentFromLocalDbLocked); + okContent = LockAndLoadContent(scope => LoadContentFromLocalDbLocked(true)); if (!okContent) _logger.Warn("Loading content from local db raised warnings, will reload from database."); - okMedia = LockAndLoadMedia(LoadMediaFromLocalDbLocked); + okMedia = LockAndLoadMedia(scope => LoadMediaFromLocalDbLocked(true)); if (!okMedia) _logger.Warn("Loading media from local db raised warnings, will reload from database."); } if (!okContent) - LockAndLoadContent(LoadContentFromDatabaseLocked); + LockAndLoadContent(scope => LoadContentFromDatabaseLocked(scope, true)); if (!okMedia) - LockAndLoadMedia(LoadMediaFromDatabaseLocked); + LockAndLoadMedia(scope => LoadMediaFromDatabaseLocked(scope, true)); LockAndLoadDomains(); } @@ -333,7 +333,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - private bool LoadContentFromDatabaseLocked(IScope scope) + private bool LoadContentFromDatabaseLocked(IScope scope, bool onStartup) { // locks: // contentStore is wlocked (1 thread) @@ -353,11 +353,11 @@ namespace Umbraco.Web.PublishedCache.NuCache // IMPORTANT GetAllContentSources sorts kits by level + parentId + sortOrder var kits = _dataSource.GetAllContentSources(scope); - return _contentStore.SetAllFastSorted(kits); + return onStartup ? _contentStore.SetAllFastSorted(kits) : _contentStore.SetAll(kits); } } - private bool LoadContentFromLocalDbLocked(IScope scope) + private bool LoadContentFromLocalDbLocked(bool onStartup) { var contentTypes = _serviceContext.ContentTypeService.GetAll() .Select(x => _publishedContentTypeFactory.CreateContentType(x)); @@ -372,7 +372,7 @@ namespace Umbraco.Web.PublishedCache.NuCache .OrderBy(x => x.Node.Level) .ThenBy(x => x.Node.ParentContentId) .ThenBy(x => x.Node.SortOrder); // IMPORTANT sort by level + parentId + sortOrder - return _contentStore.SetAllFastSorted(kits); + return onStartup ? _contentStore.SetAllFastSorted(kits) : _contentStore.SetAll(kits); } } @@ -411,7 +411,7 @@ namespace Umbraco.Web.PublishedCache.NuCache } } - private bool LoadMediaFromDatabaseLocked(IScope scope) + private bool LoadMediaFromDatabaseLocked(IScope scope, bool onStartup) { // locks & notes: see content @@ -429,11 +429,11 @@ namespace Umbraco.Web.PublishedCache.NuCache _logger.Debug("Loading media from database..."); // IMPORTANT GetAllMediaSources sorts kits by level + parentId + sortOrder var kits = _dataSource.GetAllMediaSources(scope); - return _mediaStore.SetAllFastSorted(kits); + return onStartup ? _mediaStore.SetAllFastSorted(kits) : _mediaStore.SetAll(kits); } } - private bool LoadMediaFromLocalDbLocked(IScope scope) + private bool LoadMediaFromLocalDbLocked(bool onStartup) { var mediaTypes = _serviceContext.MediaTypeService.GetAll() .Select(x => _publishedContentTypeFactory.CreateContentType(x)); @@ -448,7 +448,7 @@ namespace Umbraco.Web.PublishedCache.NuCache .OrderBy(x => x.Node.Level) .ThenBy(x => x.Node.ParentContentId) .ThenBy(x => x.Node.SortOrder); // IMPORTANT sort by level + parentId + sortOrder - return _mediaStore.SetAllFastSorted(kits); + return onStartup ? _mediaStore.SetAllFastSorted(kits) : _mediaStore.SetAll(kits); } } @@ -628,7 +628,7 @@ namespace Umbraco.Web.PublishedCache.NuCache using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.ContentTree); - LoadContentFromDatabaseLocked(scope); + LoadContentFromDatabaseLocked(scope, false); scope.Complete(); } draftChanged = publishedChanged = true; @@ -721,7 +721,7 @@ namespace Umbraco.Web.PublishedCache.NuCache using (var scope = _scopeProvider.CreateScope()) { scope.ReadLock(Constants.Locks.MediaTree); - LoadMediaFromDatabaseLocked(scope); + LoadMediaFromDatabaseLocked(scope, false); scope.Complete(); } anythingChanged = true;