You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-issues@jackrabbit.apache.org by "Davide Giannella (JIRA)" <ji...@apache.org> on 2018/04/26 13:22:13 UTC
[jira] [Closed] (OAK-7388) MergingNodeStateDiff may recreate nodes
that were previously removed to resolve conflicts
[ https://issues.apache.org/jira/browse/OAK-7388?page=com.atlassian.jira.plugin.system.issuetabpanels:all-tabpanel ]
Davide Giannella closed OAK-7388.
---------------------------------
Bulk close 1.9.0
> MergingNodeStateDiff may recreate nodes that were previously removed to resolve conflicts
> -----------------------------------------------------------------------------------------
>
> Key: OAK-7388
> URL: https://issues.apache.org/jira/browse/OAK-7388
> Project: Jackrabbit Oak
> Issue Type: Improvement
> Components: core
> Reporter: Francesco Mari
> Assignee: Francesco Mari
> Priority: Major
> Fix For: 1.9.0, 1.10
>
>
> {{MergingNodeStateDiff}} might behave incorrectly when the resolution of a conflict involves the deletion of the conflicting node. I spotted this issue in a use case that can be expressed by the following code.
> {noformat}
> NodeState root = EmptyNodeState.EMPTY_NODE;
> NodeState withProperty;
> {
> NodeBuilder builder = root.builder();
> builder.child("c").setProperty("foo", "bar");
> withProperty = builder.getNodeState();
> }
> NodeState withUpdatedProperty;
> {
> NodeBuilder builder = withProperty.builder();
> builder.child("c").setProperty("foo", "baz");
> withUpdatedProperty = builder.getNodeState();
> }
> NodeState withRemovedChild;
> {
> NodeBuilder builder = withProperty.builder();
> builder.child("c").remove();
> withRemovedChild = builder.getNodeState();
> }
> NodeBuilder mergedBuilder = withUpdatedProperty.builder();
> withRemovedChild.compareAgainstBaseState(withProperty, new ConflictAnnotatingRebaseDiff(mergedBuilder));
> NodeState merged = ConflictHook.of(DefaultThreeWayConflictHandler.OURS).processCommit(
> mergedBuilder.getBaseState(),
> mergedBuilder.getNodeState(),
> CommitInfo.EMPTY
> );
> assertFalse(merged.hasChildNode("c"));
> {noformat}
> The assertion at the end of the code fails becauuse `merged` actually has a child node named `c`, and `c` is an empty node. After digging into the issue, I figured out that the problem is caused by the following steps.
> # {{MergingNodeStateDiff#childNodeAdded}} is invoked because of {{:conflicts}}. This eventually results in the deletion of the conflicting child node.
> # {{MergingNodeStateDiff#childNodeChanged}} is called because in {{ModifiedNodeState#compareAgainstBaseState}} the children are compared with the {{!=}} operator instead of using {{Object#equals}}.
> # {{org.apache.jackrabbit.oak.spi.state.NodeBuilder#child}} is called in order to setup a new {{MergingNodeStateDiff}} to descend into the subtree that was detected as modified.
> # {{MemoryNodeBuilder#hasChildNode}} correctly returns {{false}}, because the child was removed in step 1. The return value of {{false}} triggers the next step.
> # {{MemoryNodeBuilder#setChildNode(java.lang.String)}} is invoked in order to setup a new, empty child node.
> In other words, the snippet above can be rewritten like the following.
> {noformat}
> NodeState root = EmptyNodeState.EMPTY_NODE;
> NodeState withProperty;
> {
> NodeBuilder builder = root.builder();
> builder.child("c").setProperty("foo", "bar");
> withProperty = builder.getNodeState();
> }
> NodeState withUpdatedProperty;
> {
> NodeBuilder builder = withProperty.builder();
> builder.child("c").setProperty("foo", "baz");
> withUpdatedProperty = builder.getNodeState();
> }
> NodeState withRemovedChild;
> {
> NodeBuilder builder = withProperty.builder();
> builder.child("c").remove();
> withRemovedChild = builder.getNodeState();
> }
> NodeBuilder mergedBuilder = withUpdatedProperty.builder();
> // As per MergingNodeStateDiff.childNodeAdded()
> mergedBuilder.child("c").remove();
> // As per ModifiedNodeState#compareAgainstBaseState()
> if (withUpdatedProperty.getChildNode("c") != withRemovedChild.getChildNode("c")) {
> // As per MergingNodeStateDiff.childNodeChanged()
> mergedBuilder.child("c");
> }
> NodeState merged = mergedBuilder.getNodeState();
> assertFalse(merged.hasChildNode("c"));
> {noformat}
> The end result is that {{MergingNodeStateDiff}} inadvertently adds the node that was removed in order to resolve a conflict.
--
This message was sent by Atlassian JIRA
(v7.6.3#76005)