BUG-4278: Improve TOUCH operation effects 44/26644/4
authorRobert Varga <rovarga@cisco.com>
Tue, 8 Sep 2015 09:31:14 +0000 (11:31 +0200)
committerGerrit Code Review <gerrit@opendaylight.org>
Thu, 10 Sep 2015 07:12:05 +0000 (07:12 +0000)
The implementation of TOUCH operation would bump the subtree version of
the node even if there were no children coming in, resulting in needless
promotion of LazyContainerNodes to MaterializedContainerNodes.

If a TOUCH operation does not result in any of the children being
modified, the subtree version should not be bumped, since the subtree
was not changed. This should be the case if the user-specified children
are empty and also if the all nodes end up not modifying the data tree.

Detect both conditions and reuse the original metadata node instead of
replacing it -- this preventing LazyContainerNode promotion.

Change-Id: Icdbde02e7c3f503a9ada28b58fd27e3e3a3ef812
Signed-off-by: Robert Varga <rovarga@cisco.com>
yang/yang-data-api/src/main/java/org/opendaylight/yangtools/yang/data/api/schema/tree/spi/TreeNode.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/AbstractNodeContainerModificationStrategy.java

index e1daad6067782b320c53aec66011a7118a422913..0ac7d27a4f812ffb896ff938b761d30a6642342d 100644 (file)
@@ -7,27 +7,38 @@
  */
 package org.opendaylight.yangtools.yang.data.api.schema.tree.spi;
 
-
 import org.opendaylight.yangtools.concepts.Identifiable;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.StoreTreeNode;
 
 /**
- * A very basic data tree node. It has a version (when it was last modified),
- * a subtree version (when any of its children were modified) and some read-only
- * data.
+ * A very basic data tree node. It has a version (when it was last modified), a subtree version (when any of its
+ * children were modified) and some read-only data.
+ *
+ * Semantic difference between these two is important when dealing with modifications involving parent/child
+ * relationships and what operations can be execute concurrently without creating a data dependency conflict.
+ *
+ * A replace/delete operation cannot be applied to this node if the subtree version does not match. This mismatch
+ * still allows modifications to its descendants.
+ *
+ * A mismatch in node version indicates a replacement, preventing a modification of descendants or itself.
  */
+// FIXME: BUG-2399: clarify that versioning rules are not enforced for non-presence containers, as they are not
+//                  considered to be data nodes.
 public interface TreeNode extends Identifiable<PathArgument>, StoreTreeNode<TreeNode> {
     /**
-     * Get the data node version.
+     * Get the data node version. This version is updated whenever the data representation of this particular node
+     * changes as a result of a direct write to this node or to its parent nodes -- thus indicating that this node
+     * was logically replaced.
      *
      * @return Current data node version.
      */
     Version getVersion();
 
     /**
-     * Get the subtree version.
+     * Get the subtree version. This version is updated whenever the data representation of this particular node
+     * changes as the result of a direct or indirect child node being created, replaced or removed.
      *
      * @return Current subtree version.
      */
index 07f04ccc816a5172b5b403fc774ca7fdd00fbaee..5a8bf47aefc2524b8a387e4ac3afa23e3fc18fe1 100644 (file)
@@ -8,7 +8,6 @@
 package org.opendaylight.yangtools.yang.data.impl.schema.tree;
 
 import static com.google.common.base.Preconditions.checkArgument;
-
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
 import java.util.Collection;
@@ -89,8 +88,12 @@ abstract class AbstractNodeContainerModificationStrategy extends SchemaAwareAppl
 
         @SuppressWarnings("rawtypes")
         final NormalizedNodeContainerBuilder dataBuilder = createBuilder(newValue);
+        final TreeNode result = mutateChildren(mutable, dataBuilder, version, modification.getChildren());
 
-        return mutateChildren(mutable, dataBuilder, version, modification.getChildren());
+        // We are good to go except one detail: this is a single logical write, but
+        // we have a result TreeNode which has been forced to materialized, e.g. it
+        // is larger than it needs to be. Create a new TreeNode to host the data.
+        return TreeNodeFactory.createTreeNode(result.getData(), version);
     }
 
     /**
@@ -134,45 +137,41 @@ abstract class AbstractNodeContainerModificationStrategy extends SchemaAwareAppl
     }
 
     @Override
-    public TreeNode applyTouch(final ModifiedNode modification,
-            final TreeNode currentMeta, final Version version) {
-        final MutableTreeNode newMeta = currentMeta.mutable();
-        newMeta.setSubtreeVersion(version);
-
+    public TreeNode applyTouch(final ModifiedNode modification, final TreeNode currentMeta, final Version version) {
         /*
-         * The user has issued an empty merge operation. In this case we do not perform
+         * The user may have issued an empty merge operation. In this case we do not perform
          * a data tree mutation, do not pass GO, and do not collect useless garbage. It
          * also means the ModificationType is UNMODIFIED.
          */
         final Collection<ModifiedNode> children = modification.getChildren();
-        if (children.isEmpty()) {
-            modification.resolveModificationType(ModificationType.UNMODIFIED);
-            newMeta.setData(currentMeta.getData());
-            return newMeta.seal();
-        }
-
-        @SuppressWarnings("rawtypes")
-        final NormalizedNodeContainerBuilder dataBuilder = createBuilder(currentMeta.getData());
-        final TreeNode ret = mutateChildren(newMeta, dataBuilder, version, children);
-
-        /*
-         * It is possible that the only modifications under this node were empty merges,
-         * which were turned into UNMODIFIED. If that is the case, we can turn this operation
-         * into UNMODIFIED, too, potentially cascading it up to root. This has the benefit
-         * of speeding up any users, who can skip processing child nodes.
-         *
-         * In order to do that, though, we have to check all child operations are UNMODIFIED.
-         * Let's do precisely that, stopping as soon we find a different result.
-         */
-        for (final ModifiedNode child : children) {
-            if (child.getModificationType() != ModificationType.UNMODIFIED) {
-                modification.resolveModificationType(ModificationType.SUBTREE_MODIFIED);
-                return ret;
+        if (!children.isEmpty()) {
+            @SuppressWarnings("rawtypes")
+            final NormalizedNodeContainerBuilder dataBuilder = createBuilder(currentMeta.getData());
+            final MutableTreeNode newMeta = currentMeta.mutable();
+            newMeta.setSubtreeVersion(version);
+            final TreeNode ret = mutateChildren(newMeta, dataBuilder, version, children);
+
+            /*
+             * It is possible that the only modifications under this node were empty merges,
+             * which were turned into UNMODIFIED. If that is the case, we can turn this operation
+             * into UNMODIFIED, too, potentially cascading it up to root. This has the benefit
+             * of speeding up any users, who can skip processing child nodes.
+             *
+             * In order to do that, though, we have to check all child operations are UNMODIFIED.
+             * Let's do precisely that, stopping as soon we find a different result.
+             */
+            for (final ModifiedNode child : children) {
+                if (child.getModificationType() != ModificationType.UNMODIFIED) {
+                    modification.resolveModificationType(ModificationType.SUBTREE_MODIFIED);
+                    return ret;
+                }
             }
         }
 
+        // The merge operation did not have any children, or all of them turned out to be UNMODIFIED, hence do not
+        // replace the metadata node.
         modification.resolveModificationType(ModificationType.UNMODIFIED);
-        return ret;
+        return currentMeta;
     }
 
     @Override