Do not retain leaf nodes in containers by default 15/84015/9
authorRobert Varga <robert.varga@pantheon.tech>
Tue, 27 Aug 2019 14:04:55 +0000 (16:04 +0200)
committerRobert Varga <robert.varga@pantheon.tech>
Thu, 29 Aug 2019 08:23:12 +0000 (10:23 +0200)
Leaf nodes are observed to dominate our memory footprint. This
patch modifies immutable implementations of NormalizedNodeContainers
such that they eliminate any leaf nodes when they are stored, retaining
only the encapsulated value. Leaf nodes are then re-created on access
as needed.

Note this changes two aspects of operation:

1) looking up a leaf will yield a new object every time it is invoked,
   hence callers must not rely on on returned objects to be identical.

2) getValue() is operating on the backing map's entrySet() and requires
   its transformation. This means that multiple iterations over values
   will not necessarily yield same objects.

Neither of these violates effective immutability contract of NormalizeNode,
as the returned objects will compare as equal.

This behavior can be switched off at runtime by setting the system property:

    org.opendaylight.yangtools.yang.data.impl.schema.nodes.lazy-leaves=false

JIRA: YANGTOOLS-1019
Change-Id: I00fc3ac0b64290068e8a6e4c8972454729fa9011
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/builder/impl/AbstractImmutableDataContainerNodeBuilder.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/builder/impl/ImmutableAugmentationNodeBuilder.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/builder/impl/ImmutableChoiceNodeBuilder.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/builder/impl/ImmutableContainerNodeBuilder.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/builder/impl/ImmutableMapEntryNodeBuilder.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/builder/impl/ImmutableUnkeyedListEntryNodeBuilder.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/builder/impl/ImmutableYangModeledAnyXmlNodeBuilder.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/nodes/AbstractImmutableDataContainerNode.java
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/nodes/LazyLeafOperations.java [new file with mode: 0644]
yang/yang-data-jaxen/src/test/java/org/opendaylight/yangtools/yang/data/jaxen/DerefXPathFunctionTest.java

index f442d673a7f430989d69965561503ba68a7616f6..dc8cf1e33c10d7e63b5bc8eb7ce473b18a6189d4 100644 (file)
@@ -21,6 +21,7 @@ import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContaine
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.NormalizedNodeContainerBuilder;
 import org.opendaylight.yangtools.yang.data.impl.schema.nodes.AbstractImmutableDataContainerNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.nodes.CloneableMap;
+import org.opendaylight.yangtools.yang.data.impl.schema.nodes.LazyLeafOperations;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
@@ -42,7 +43,7 @@ abstract class AbstractImmutableDataContainerNodeBuilder<I extends PathArgument,
         }
     }
 
-    private Map<PathArgument, DataContainerChild<? extends PathArgument, ?>> value;
+    private Map<PathArgument, Object> value;
     private I nodeIdentifier;
 
     /*
@@ -86,13 +87,12 @@ abstract class AbstractImmutableDataContainerNodeBuilder<I extends PathArgument,
     }
 
     protected final DataContainerChild<? extends PathArgument, ?> getChild(final PathArgument child) {
-        return value.get(child);
+        return LazyLeafOperations.getChild(value, child);
     }
 
-    protected final Map<PathArgument, DataContainerChild<? extends PathArgument, ?>> buildValue() {
+    protected final Map<PathArgument, Object> buildValue() {
         if (value instanceof ModifiableMapPhase) {
-            return ((ModifiableMapPhase<PathArgument, DataContainerChild<? extends PathArgument, ?>>)value)
-                    .toUnmodifiableMap();
+            return ((ModifiableMapPhase<PathArgument, Object>)value).toUnmodifiableMap();
         }
 
         dirty = true;
@@ -102,11 +102,9 @@ abstract class AbstractImmutableDataContainerNodeBuilder<I extends PathArgument,
     private void checkDirty() {
         if (dirty) {
             if (value instanceof UnmodifiableMapPhase) {
-                value = ((UnmodifiableMapPhase<PathArgument, DataContainerChild<? extends PathArgument, ?>>) value)
-                        .toModifiableMap();
+                value = ((UnmodifiableMapPhase<PathArgument, Object>) value).toModifiableMap();
             } else if (value instanceof CloneableMap) {
-                value = ((CloneableMap<PathArgument, DataContainerChild<? extends PathArgument, ?>>) value)
-                        .createMutableClone();
+                value = ((CloneableMap<PathArgument, Object>) value).createMutableClone();
             } else {
                 value = newHashMap(value);
             }
@@ -127,7 +125,7 @@ abstract class AbstractImmutableDataContainerNodeBuilder<I extends PathArgument,
     @Override
     public DataContainerNodeBuilder<I, R> withChild(final DataContainerChild<?, ?> child) {
         checkDirty();
-        this.value.put(child.getIdentifier(), child);
+        LazyLeafOperations.putChild(value, child);
         return this;
     }
 
index 8f4357d24c0361b244c323d631e12d6880c78a39..912cdc487aac2452013f2cac2376e7a4e529d221 100644 (file)
@@ -74,7 +74,7 @@ public class ImmutableAugmentationNodeBuilder
             extends AbstractImmutableDataContainerNode<AugmentationIdentifier> implements AugmentationNode {
 
         ImmutableAugmentationNode(final AugmentationIdentifier nodeIdentifier,
-                final Map<PathArgument, DataContainerChild<? extends PathArgument, ?>> children) {
+                final Map<PathArgument, Object> children) {
             super(children, nodeIdentifier);
         }
     }
index 61d14c614dac1419c01efd1414567f808a1ce7c2..80be092d32027bb7463aca069f2eb2d42ff58d90 100644 (file)
@@ -12,7 +12,6 @@ import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.ChoiceNode;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
 import org.opendaylight.yangtools.yang.data.impl.schema.nodes.AbstractImmutableDataContainerNode;
 
@@ -54,8 +53,7 @@ public class ImmutableChoiceNodeBuilder extends AbstractImmutableDataContainerNo
     private static final class ImmutableChoiceNode extends AbstractImmutableDataContainerNode<NodeIdentifier>
             implements ChoiceNode {
 
-        ImmutableChoiceNode(final NodeIdentifier nodeIdentifier,
-                final Map<PathArgument, DataContainerChild<? extends PathArgument, ?>> children) {
+        ImmutableChoiceNode(final NodeIdentifier nodeIdentifier, final Map<PathArgument, Object> children) {
             super(children, nodeIdentifier);
         }
     }
index ec821098a69447ede1ca7979ff7068ae0241abf9..caa1e7d08033137bdee10cbeecc362d3d319bc46 100644 (file)
@@ -12,7 +12,6 @@ import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
 import org.opendaylight.yangtools.yang.data.impl.schema.nodes.AbstractImmutableDataContainerNode;
 
@@ -55,8 +54,7 @@ public class ImmutableContainerNodeBuilder
     protected static final class ImmutableContainerNode extends AbstractImmutableDataContainerNode<NodeIdentifier>
             implements ContainerNode {
 
-        ImmutableContainerNode(final NodeIdentifier nodeIdentifier,
-                final Map<PathArgument, DataContainerChild<? extends PathArgument, ?>> children) {
+        ImmutableContainerNode(final NodeIdentifier nodeIdentifier, final Map<PathArgument, Object> children) {
             super(children, nodeIdentifier);
         }
     }
index 6e6b7a59d48f9d8a3e59f7512d2f011ab0f21be9..33fd8a8ef122d53b9f5b689a382f4ee8f7ac4121 100644 (file)
@@ -78,7 +78,6 @@ public class ImmutableMapEntryNodeBuilder
         }
     }
 
-
     @Override
     public DataContainerNodeBuilder<NodeIdentifierWithPredicates, MapEntryNode> withValue(
             final Collection<DataContainerChild<? extends PathArgument, ?>> withValue) {
@@ -124,7 +123,7 @@ public class ImmutableMapEntryNodeBuilder
             extends AbstractImmutableDataContainerNode<NodeIdentifierWithPredicates> implements MapEntryNode {
 
         ImmutableMapEntryNode(final NodeIdentifierWithPredicates nodeIdentifier,
-                final Map<PathArgument, DataContainerChild<? extends PathArgument, ?>> children) {
+                final Map<PathArgument, Object> children) {
             super(children, nodeIdentifier);
         }
     }
index 599789a51745ee3ea0f67052875b53d306a3aeb5..93823e871edaebb43f1bda8eda246d6c589d5ebb 100644 (file)
@@ -11,7 +11,6 @@ import java.util.Map;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
 import org.opendaylight.yangtools.yang.data.api.schema.UnkeyedListEntryNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
 import org.opendaylight.yangtools.yang.data.impl.schema.nodes.AbstractImmutableDataContainerNode;
@@ -56,8 +55,7 @@ public class ImmutableUnkeyedListEntryNodeBuilder
     protected static final class ImmutableUnkeyedListEntryNode
             extends AbstractImmutableDataContainerNode<NodeIdentifier> implements UnkeyedListEntryNode {
 
-        ImmutableUnkeyedListEntryNode(final NodeIdentifier nodeIdentifier,
-                final Map<PathArgument, DataContainerChild<? extends PathArgument, ?>> children) {
+        ImmutableUnkeyedListEntryNode(final NodeIdentifier nodeIdentifier, final Map<PathArgument, Object> children) {
             super(children, nodeIdentifier);
         }
     }
index 2916a50a2c7f42ed93ae0bb705eaf1ebb7cba2a3..3abeea822086f7d0410775cb811e2c9785d8f5d7 100644 (file)
@@ -14,7 +14,6 @@ import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.yangtools.odlext.model.api.YangModeledAnyXmlSchemaNode;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
-import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
 import org.opendaylight.yangtools.yang.data.api.schema.YangModeledAnyXmlNode;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.api.DataContainerNodeBuilder;
 import org.opendaylight.yangtools.yang.data.impl.schema.nodes.AbstractImmutableDataContainerNode;
@@ -59,8 +58,7 @@ public final class ImmutableYangModeledAnyXmlNodeBuilder extends
 
         private final @NonNull ContainerSchemaNode contentSchema;
 
-        ImmutableYangModeledAnyXmlNode(final NodeIdentifier nodeIdentifier,
-                final Map<PathArgument, DataContainerChild<? extends PathArgument, ?>> value,
+        ImmutableYangModeledAnyXmlNode(final NodeIdentifier nodeIdentifier, final Map<PathArgument, Object> value,
                 final ContainerSchemaNode contentSchema) {
             super(value, nodeIdentifier);
             this.contentSchema = requireNonNull(contentSchema, "Schema of yang modeled anyXml content cannot be null.");
index 231a703e3c5912a2036980500090836d7beb924c..dabcc5777eaeee7af57d56aefe9635ce9dfb455b 100644 (file)
@@ -18,10 +18,9 @@ import org.opendaylight.yangtools.yang.data.api.schema.DataContainerNode;
 public abstract class AbstractImmutableDataContainerNode<K extends PathArgument>
         extends AbstractImmutableNormalizedNode<K, Collection<DataContainerChild<? extends PathArgument, ?>>>
         implements DataContainerNode<K> {
-    private final Map<PathArgument, DataContainerChild<? extends PathArgument, ?>> children;
+    private final Map<PathArgument, Object> children;
 
-    public AbstractImmutableDataContainerNode(
-            final Map<PathArgument, DataContainerChild<? extends PathArgument, ?>> children, final K nodeIdentifier) {
+    protected AbstractImmutableDataContainerNode(final Map<PathArgument, Object> children, final K nodeIdentifier) {
         super(nodeIdentifier);
 
         this.children = ImmutableOffsetMap.unorderedCopyOf(children);
@@ -29,12 +28,12 @@ public abstract class AbstractImmutableDataContainerNode<K extends PathArgument>
 
     @Override
     public final Optional<DataContainerChild<? extends PathArgument, ?>> getChild(final PathArgument child) {
-        return Optional.ofNullable(children.get(child));
+        return LazyLeafOperations.findChild(children, child);
     }
 
     @Override
     public final Collection<DataContainerChild<? extends PathArgument, ?>> getValue() {
-        return children.values();
+        return LazyLeafOperations.getValue(children);
     }
 
     @Override
@@ -46,13 +45,12 @@ public abstract class AbstractImmutableDataContainerNode<K extends PathArgument>
      * DO NOT USE THIS METHOD.
      *
      * <p>
-     * This is an implementation-internal API and no outside users should use it. If you do,
-     * you are asking for trouble, as the returned object is not guaranteed to conform to
-     * java.util.Map interface.
+     * This is an implementation-internal API and no outside users should use it. If you do, you are asking for trouble,
+     * as the returned object is not guaranteed to conform to java.util.Map interface, nor is its contents well-defined.
      *
      * @return An unmodifiable view if this node's children.
      */
-    public final Map<PathArgument, DataContainerChild<? extends PathArgument, ?>> getChildren() {
+    public final Map<PathArgument, Object> getChildren() {
         return children;
     }
 
@@ -60,6 +58,5 @@ public abstract class AbstractImmutableDataContainerNode<K extends PathArgument>
     protected boolean valueEquals(final AbstractImmutableNormalizedNode<?, ?> other) {
         return other instanceof AbstractImmutableDataContainerNode<?> && children.equals(
                 ((AbstractImmutableDataContainerNode<?>) other).children);
-
     }
 }
diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/nodes/LazyLeafOperations.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/nodes/LazyLeafOperations.java
new file mode 100644 (file)
index 0000000..868b014
--- /dev/null
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2019 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.yangtools.yang.data.impl.schema.nodes;
+
+import static com.google.common.base.Verify.verify;
+import static java.util.Objects.requireNonNull;
+
+import com.google.common.annotations.Beta;
+import com.google.common.collect.Collections2;
+import java.util.Collection;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Optional;
+import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
+import org.opendaylight.yangtools.yang.data.api.schema.DataContainerChild;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.LeafSetEntryNode;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Support utilities for dealing with Maps which would normally hold {@link DataContainerChild} values, but are modified
+ * to eliminate {@link LeafNode} instances.
+ *
+ * <p>
+ * This class holds implementation logic which controls lifecycle of {@link LeafNode}s  by providing a central policy
+ * point for how the implementation treats these nodes. There are two modes of operation:
+ * <ul>
+ *   <li>eager, which means leaf nodes are retained by their parent<li>
+ *   <li>lazy, which means leaf nodes are created whenever they are queried and no attempt is made to retain them</li>
+ * </ul>
+ *
+ * <p>
+ * Selection of the mode in effect is available through {@value #EXPENDABLE_PROP_NAME} system property.
+ */
+@Beta
+public final class LazyLeafOperations {
+    private static final Logger LOG = LoggerFactory.getLogger(LazyLeafOperations.class);
+    private static final String EXPENDABLE_PROP_NAME =
+            "org.opendaylight.yangtools.yang.data.impl.schema.nodes.lazy-leaves";
+
+    /**
+     * Global enabled run-time constant. If set to true, this class will treat {@link LeafNode} and
+     * {@link LeafSetEntryNode} as an expendable object. This constant is controlled by {@value #EXPENDABLE_PROP_NAME}
+     * system property.
+     */
+    private static final boolean EXPENDABLE;
+
+    static {
+        EXPENDABLE = Boolean.parseBoolean(System.getProperty(EXPENDABLE_PROP_NAME, "true"));
+        LOG.info("Leaf nodes are treated as {} nodes", EXPENDABLE ? "transient" : "regular");
+    }
+
+    private LazyLeafOperations() {
+
+    }
+
+    public static Optional<DataContainerChild<?, ?>> findChild(final Map<PathArgument, Object> map,
+            final PathArgument key) {
+        final Object value = map.get(key);
+        return value == null ? Optional.empty() : Optional.of(decodeChild(key, value));
+    }
+
+    public static @Nullable DataContainerChild<?, ?> getChild(final Map<PathArgument, Object> map,
+            final PathArgument key) {
+        final Object value = map.get(key);
+        return value == null ? null : decodeChild(key, value);
+    }
+
+    public static void putChild(final Map<PathArgument, Object> map, final DataContainerChild<?, ?> child) {
+        final DataContainerChild<?, ?> node = requireNonNull(child);
+        map.put(child.getIdentifier(), EXPENDABLE ? encodeExpendableChild(node) : node);
+    }
+
+    @SuppressWarnings({ "rawtypes", "unchecked" })
+    public static @NonNull Collection<DataContainerChild<?, ?>> getValue(final Map<PathArgument, Object> map) {
+        return EXPENDABLE ? Collections2.transform(map.entrySet(), LazyLeafOperations::decodeEntry)
+                // This is an ugly cast, but it is accurate IFF all modifications are done through this class
+                : (Collection) map.values();
+    }
+
+    private static @NonNull Object encodeExpendableChild(final DataContainerChild<?, ?> key) {
+        return key instanceof LeafNode ? ((LeafNode<?>) key).getValue() : requireNonNull(key);
+    }
+
+    private static @Nullable DataContainerChild<?, ?> decodeChild(final PathArgument key, final @NonNull Object value) {
+        return EXPENDABLE ? decodeExpendableChild(key, value) : verifyCast(value);
+    }
+
+    private static @NonNull DataContainerChild<?, ?> decodeExpendableChild(final PathArgument key,
+            @NonNull final Object value) {
+        return value instanceof DataContainerChild ? (DataContainerChild<?, ?>) value  : coerceLeaf(key, value);
+    }
+
+    private static @NonNull DataContainerChild<?, ?> verifyCast(final @NonNull Object value) {
+        verify(value instanceof DataContainerChild, "Unexpected child %s", value);
+        return (DataContainerChild<?, ?>)value;
+    }
+
+    private static @NonNull DataContainerChild<? extends PathArgument, ?> decodeEntry(
+            final @NonNull Entry<PathArgument, Object> entry) {
+        final Object value = entry.getValue();
+        return value instanceof DataContainerChild ? (DataContainerChild<?, ?>) value
+                : coerceLeaf(entry.getKey(), value);
+    }
+
+    private static @NonNull LeafNode<?> coerceLeaf(final PathArgument key, final Object value) {
+        verify(key instanceof NodeIdentifier, "Unexpected value %s for child %s", value, key);
+        return ImmutableNodes.leafNode((NodeIdentifier) key, value);
+    }
+}
index 24a08803a13eafbb3f1c7b4773de8fcc94cb0cad..e383694cc7738a82b094b65a94807369e7e9194e 100644 (file)
@@ -8,6 +8,7 @@
 
 package org.opendaylight.yangtools.yang.data.jaxen;
 
+import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertSame;
 import static org.junit.Assert.assertTrue;
@@ -87,7 +88,7 @@ public class DerefXPathFunctionTest {
         final Object derefResult = derefFunction.call(normalizedNodeContext, ImmutableList.of());
         assertNotNull(derefResult);
         assertTrue(derefResult instanceof NormalizedNode<?, ?>);
-        assertSame(referencedLeafNode, derefResult);
+        assertEquals(referencedLeafNode, derefResult);
     }
 
     @Test
@@ -120,7 +121,7 @@ public class DerefXPathFunctionTest {
         Object derefResult = derefFunction.call(normalizedNodeContext, ImmutableList.of());
         assertNotNull(derefResult);
         assertTrue(derefResult instanceof NormalizedNode<?, ?>);
-        assertSame(referencedLeafNode, derefResult);
+        assertEquals(referencedLeafNode, derefResult);
 
         final YangInstanceIdentifier relLeafrefPath = YangInstanceIdentifier.of(MY_INNER_CONTAINER)
                 .node(REL_LEAFREF_LEAF);
@@ -129,7 +130,7 @@ public class DerefXPathFunctionTest {
         derefResult = derefFunction.call(normalizedNodeContext, ImmutableList.of());
         assertNotNull(derefResult);
         assertTrue(derefResult instanceof NormalizedNode<?, ?>);
-        assertSame(referencedLeafNode, derefResult);
+        assertEquals(referencedLeafNode, derefResult);
     }
 
     @Test