Fixes regression issue in nucache causing a panic exception

This commit is contained in:
Shannon
2019-09-25 13:51:19 +02:00
parent f2d8d3f44f
commit 1caf7f4587
3 changed files with 195 additions and 30 deletions

View File

@@ -108,8 +108,8 @@ namespace Umbraco.Tests.PublishedContent
);
// create a scope provider
var scopeProvider = Mock.Of<IScopeProvider>();
Mock.Get(scopeProvider)
var scopeProvider = new Mock<IScopeProvider>();
scopeProvider
.Setup(x => x.CreateScope(
It.IsAny<IsolationLevel>(),
It.IsAny<RepositoryCacheMode>(),
@@ -143,7 +143,7 @@ namespace Umbraco.Tests.PublishedContent
_snapshotAccessor,
_variationAccesor,
Mock.Of<IProfilingLogger>(),
scopeProvider,
scopeProvider.Object,
Mock.Of<IDocumentRepository>(),
Mock.Of<IMediaRepository>(),
Mock.Of<IMemberRepository>(),
@@ -956,8 +956,10 @@ namespace Umbraco.Tests.PublishedContent
}
[Test]
public void Issue6353()
public void Set_All_Fast_Sorted_Ensure_LastChildContentId()
{
//see https://github.com/umbraco/Umbraco-CMS/issues/6353
IEnumerable<ContentNodeKit> GetKits()
{
var paths = new Dictionary<int, string> { { -1, "-1" } };
@@ -968,28 +970,181 @@ namespace Umbraco.Tests.PublishedContent
Init(GetKits());
var snapshotService = (PublishedSnapshotService) _snapshotService;
var contentStoreField = typeof(PublishedSnapshotService).GetField("_contentStore", BindingFlags.Instance | BindingFlags.NonPublic);
var contentStore = (ContentStore) contentStoreField.GetValue(snapshotService);
var contentNodesField = typeof(ContentStore).GetField("_contentNodes", BindingFlags.Instance | BindingFlags.NonPublic);
var contentNodes = (ConcurrentDictionary<int, LinkedNode<ContentNode>>) contentNodesField.GetValue(contentStore);
var snapshotService = (PublishedSnapshotService)_snapshotService;
var contentStore = snapshotService.GetContentStore();
var parentNode = contentNodes[1].Value;
Assert.AreEqual(-1, parentNode.PreviousSiblingContentId);
Assert.AreEqual(-1, parentNode.NextSiblingContentId);
Assert.AreEqual(2, parentNode.FirstChildContentId);
Assert.AreEqual(2, parentNode.LastChildContentId);
var parentNodes = contentStore.Test.GetValues(1);
var parentNode = parentNodes[0];
AssertLinkedNode(parentNode.contentNode, -1, -1, -1, 2, 2);
_snapshotService.Notify(new[]
{
new ContentCacheRefresher.JsonPayload(2, TreeChangeTypes.Remove)
}, out _, out _);
parentNode = contentNodes[1].Value;
Assert.AreEqual(-1, parentNode.PreviousSiblingContentId);
Assert.AreEqual(-1, parentNode.NextSiblingContentId);
Assert.AreEqual(-1, parentNode.FirstChildContentId);
Assert.AreEqual(-1, parentNode.LastChildContentId);
parentNodes = contentStore.Test.GetValues(1);
parentNode = parentNodes[0];
AssertLinkedNode(parentNode.contentNode, -1, -1, -1, -1, -1);
}
[Test]
public void Remove_Node_Ensures_Linked_List()
{
// NOTE: these tests are not using real scopes, in which case a Scope does not control
// how the snapshots generations work. We are forcing new snapshot generations manually.
IEnumerable<ContentNodeKit> GetKits()
{
var paths = new Dictionary<int, string> { { -1, "-1" } };
//root
yield return CreateInvariantKit(1, -1, 1, paths);
//children
yield return CreateInvariantKit(2, 1, 1, paths);
yield return CreateInvariantKit(3, 1, 2, paths); //middle child
yield return CreateInvariantKit(4, 1, 3, paths);
}
Init(GetKits());
var snapshotService = (PublishedSnapshotService)_snapshotService;
var contentStore = snapshotService.GetContentStore();
Assert.AreEqual(1, contentStore.Test.LiveGen);
Assert.IsTrue(contentStore.Test.NextGen);
var parentNode = contentStore.Test.GetValues(1)[0];
Assert.AreEqual(1, parentNode.gen);
AssertLinkedNode(parentNode.contentNode, -1, -1, -1, 2, 4);
var child1 = contentStore.Test.GetValues(2)[0];
Assert.AreEqual(1, child1.gen);
AssertLinkedNode(child1.contentNode, 1, -1, 3, -1, -1);
var child2 = contentStore.Test.GetValues(3)[0];
Assert.AreEqual(1, child2.gen);
AssertLinkedNode(child2.contentNode, 1, 2, 4, -1, -1);
var child3 = contentStore.Test.GetValues(4)[0];
Assert.AreEqual(1, child3.gen);
AssertLinkedNode(child3.contentNode, 1, 3, -1, -1, -1);
//This will set a flag to force creating a new Gen next time the store is locked (i.e. In Notify)
contentStore.CreateSnapshot();
Assert.IsFalse(contentStore.Test.NextGen);
_snapshotService.Notify(new[]
{
new ContentCacheRefresher.JsonPayload(3, TreeChangeTypes.Remove) //remove middle child
}, out _, out _);
Assert.AreEqual(2, contentStore.Test.LiveGen);
Assert.IsTrue(contentStore.Test.NextGen);
var parentNodes = contentStore.Test.GetValues(1);
Assert.AreEqual(1, parentNodes.Length); // the parent doesn't get changed, not new gen's are added
parentNode = parentNodes[0];
Assert.AreEqual(1, parentNode.gen); // the parent node's gen has not changed
AssertLinkedNode(parentNode.contentNode, -1, -1, -1, 2, 4);
child1 = contentStore.Test.GetValues(2)[0];
Assert.AreEqual(2, child1.gen); // there is now 2x gen's of this item
AssertLinkedNode(child1.contentNode, 1, -1, 4, -1, -1);
child2 = contentStore.Test.GetValues(3)[0];
Assert.AreEqual(2, child2.gen); // there is now 2x gen's of this item
Assert.IsNull(child2.contentNode); // because it doesn't exist anymore
child3 = contentStore.Test.GetValues(4)[0];
Assert.AreEqual(2, child3.gen); // there is now 2x gen's of this item
AssertLinkedNode(child3.contentNode, 1, 2, -1, -1, -1);
}
[Test]
public void Refresh_Branch_Ensures_Linked_List()
{
// NOTE: these tests are not using real scopes, in which case a Scope does not control
// how the snapshots generations work. We are forcing new snapshot generations manually.
IEnumerable<ContentNodeKit> GetKits()
{
var paths = new Dictionary<int, string> { { -1, "-1" } };
//root
yield return CreateInvariantKit(1, -1, 1, paths);
//children
yield return CreateInvariantKit(2, 1, 1, paths);
yield return CreateInvariantKit(3, 1, 2, paths); //middle child
yield return CreateInvariantKit(4, 1, 3, paths);
}
Init(GetKits());
var snapshotService = (PublishedSnapshotService)_snapshotService;
var contentStore = snapshotService.GetContentStore();
Assert.AreEqual(1, contentStore.Test.LiveGen);
Assert.IsTrue(contentStore.Test.NextGen);
var parentNode = contentStore.Test.GetValues(1)[0];
Assert.AreEqual(1, parentNode.gen);
AssertLinkedNode(parentNode.contentNode, -1, -1, -1, 2, 4);
var child1 = contentStore.Test.GetValues(2)[0];
Assert.AreEqual(1, child1.gen);
AssertLinkedNode(child1.contentNode, 1, -1, 3, -1, -1);
var child2 = contentStore.Test.GetValues(3)[0];
Assert.AreEqual(1, child2.gen);
AssertLinkedNode(child2.contentNode, 1, 2, 4, -1, -1);
var child3 = contentStore.Test.GetValues(4)[0];
Assert.AreEqual(1, child3.gen);
AssertLinkedNode(child3.contentNode, 1, 3, -1, -1, -1);
//This will set a flag to force creating a new Gen next time the store is locked (i.e. In Notify)
contentStore.CreateSnapshot();
Assert.IsFalse(contentStore.Test.NextGen);
_snapshotService.Notify(new[]
{
new ContentCacheRefresher.JsonPayload(3, TreeChangeTypes.RefreshBranch) //remove middle child
}, out _, out _);
Assert.AreEqual(2, contentStore.Test.LiveGen);
Assert.IsTrue(contentStore.Test.NextGen);
var parentNodes = contentStore.Test.GetValues(1);
Assert.AreEqual(1, parentNodes.Length); // the parent doesn't get changed, not new gen's are added
parentNode = parentNodes[0];
Assert.AreEqual(1, parentNode.gen); // the parent node's gen has not changed
AssertLinkedNode(parentNode.contentNode, -1, -1, -1, 2, 4);
child1 = contentStore.Test.GetValues(2)[0];
Assert.AreEqual(2, child1.gen); // there is now 2x gen's of this item
AssertLinkedNode(child1.contentNode, 1, -1, 3, -1, -1);
child2 = contentStore.Test.GetValues(3)[0];
Assert.AreEqual(2, child2.gen); // there is now 2x gen's of this item
AssertLinkedNode(child2.contentNode, 1, 2, 4, -1, -1);
child3 = contentStore.Test.GetValues(4)[0];
Assert.AreEqual(2, child3.gen); // there is now 2x gen's of this item
AssertLinkedNode(child3.contentNode, 1, 3, -1, -1, -1);
}
private void AssertLinkedNode(ContentNode node, int parent, int prevSibling, int nextSibling, int firstChild, int lastChild)
{
Assert.AreEqual(parent, node.ParentContentId);
Assert.AreEqual(prevSibling, node.PreviousSiblingContentId);
Assert.AreEqual(nextSibling, node.NextSiblingContentId);
Assert.AreEqual(firstChild, node.FirstChildContentId);
Assert.AreEqual(lastChild, node.LastChildContentId);
}
private void AssertDocuments(IPublishedContent[] documents, params string[] names)

View File

@@ -953,8 +953,6 @@ namespace Umbraco.Web.PublishedCache.NuCache
var parent = parentLink.Value;
var currentGen = parentLink.Gen;
// if parent has no children, clone parent + add as first child
if (parent.FirstChildContentId < 0)
{
@@ -965,7 +963,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
// get parent's first child
var childLink = GetRequiredLinkedNode(parent.FirstChildContentId, "first child", currentGen);
var childLink = GetRequiredLinkedNode(parent.FirstChildContentId, "first child", null);
var child = childLink.Value;
// if first, clone parent + insert as first child
@@ -985,7 +983,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
// get parent's last child
var lastChildLink = GetRequiredLinkedNode(parent.LastChildContentId, "last child", currentGen);
var lastChildLink = GetRequiredLinkedNode(parent.LastChildContentId, "last child", null);
var lastChild = lastChildLink.Value;
// if last, clone parent + append as last child
@@ -1010,7 +1008,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
while (child.NextSiblingContentId > 0)
{
// get next child
var nextChildLink = GetRequiredLinkedNode(child.NextSiblingContentId, "next child", currentGen);
var nextChildLink = GetRequiredLinkedNode(child.NextSiblingContentId, "next child", null);
var nextChild = nextChildLink.Value;
// if here, clone previous + append/insert
@@ -1034,7 +1032,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
}
// should never get here
throw new Exception("panic: no more children.");
throw new PanicException("No more children.");
}
// replaces the root node
@@ -1370,7 +1368,7 @@ namespace Umbraco.Web.PublishedCache.NuCache
#endregion
#region Unit testing
#region Internals/Unit testing
private TestHelper _unitTesting;
@@ -1393,17 +1391,22 @@ namespace Umbraco.Web.PublishedCache.NuCache
set => _store._collectAuto = value;
}
public Tuple<long, ContentNode>[] GetValues(int id)
/// <summary>
/// Return a list of Gen/ContentNode values
/// </summary>
/// <param name="id"></param>
/// <returns></returns>
public (long gen, ContentNode contentNode)[] GetValues(int id)
{
_store._contentNodes.TryGetValue(id, out LinkedNode<ContentNode> link); // else null
if (link == null)
return new Tuple<long, ContentNode>[0];
return Array.Empty<(long, ContentNode)>();
var tuples = new List<Tuple<long, ContentNode>>();
var tuples = new List<(long, ContentNode)>();
do
{
tuples.Add(Tuple.Create(link.Gen, link.Value));
tuples.Add((link.Gen, link.Value));
link = link.Next;
} while (link != null);
return tuples.ToArray();

View File

@@ -1688,5 +1688,12 @@ AND cmsContentNu.nodeId IS NULL
}
#endregion
#region Internals/Testing
internal ContentStore GetContentStore() => _contentStore;
internal ContentStore GetMediaStore() => _mediaStore;
#endregion
}
}