Improve DtaContainerCodecProtype.loadInstance()
[mdsal.git] / binding / mdsal-binding-dom-codec / src / main / java / org / opendaylight / mdsal / binding / dom / codec / impl / DataContainerCodecPrototype.java
index 386a880e7b1e4e3a3adf2d0affb2373624edd9bb..2a6abd3d7942472a169cdb8dfbac015303d0f5e2 100644 (file)
@@ -8,7 +8,8 @@
 package org.opendaylight.mdsal.binding.dom.codec.impl;
 
 import com.google.common.collect.Iterables;
-import org.checkerframework.checker.lock.qual.Holding;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.binding.dom.codec.api.BindingDataObjectCodecTreeNode.ChildAddressabilitySummary;
 import org.opendaylight.mdsal.binding.dom.codec.impl.NodeCodecContext.CodecContextFactory;
@@ -25,10 +26,12 @@ import org.opendaylight.yangtools.yang.model.api.AnyxmlSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.AugmentationSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.CaseSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.ChoiceSchemaNode;
+import org.opendaylight.yangtools.yang.model.api.ContainerLike;
 import org.opendaylight.yangtools.yang.model.api.ContainerSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.DataNodeContainer;
 import org.opendaylight.yangtools.yang.model.api.DataSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.DocumentedNode.WithStatus;
+import org.opendaylight.yangtools.yang.model.api.EffectiveModelContext;
 import org.opendaylight.yangtools.yang.model.api.ListSchemaNode;
 import org.opendaylight.yangtools.yang.model.api.NotificationDefinition;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
@@ -39,6 +42,17 @@ import org.slf4j.LoggerFactory;
 final class DataContainerCodecPrototype<T extends WithStatus> implements NodeContextSupplier {
     private static final Logger LOG = LoggerFactory.getLogger(DataContainerCodecPrototype.class);
 
+    private static final VarHandle INSTANCE;
+
+    static {
+        try {
+            INSTANCE = MethodHandles.lookup().findVarHandle(DataContainerCodecPrototype.class,
+                "instance", DataContainerCodecContext.class);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
     private final T schema;
     private final QNameModule namespace;
     private final CodecContextFactory factory;
@@ -46,37 +60,8 @@ final class DataContainerCodecPrototype<T extends WithStatus> implements NodeCon
     private final PathArgument yangArg;
     private final ChildAddressabilitySummary childAddressabilitySummary;
 
-    /*
-     * FIXME: This field is now utterly in the hot path of CodecDataObject's instantiation of data. It is volatile
-     *        support double-checked locking, as detailed in
-     *        https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java.
-     *
-     *        Removing volatile here would seem to be worthwhile, as it would mean that in the initialized case we end
-     *        up with no happens-before synchronization between threads, allowing JIT to perform better.
-     *
-     *        Since we are on Java 5+, DataContainerCodecContext and its subclasses play a role here. All of their
-     *        fields are either final or volatile, so intuition would indicate they themselves could (already?) be
-     *        acting in the same capacity as "FinalWrapper" in the above example does -- in which case we can safely
-     *        remove the volatile access altogether, after documenting this fact there (and potentially placing some
-     *        guards).
-     *
-     *        The presence of volatile fields seems to violate this, as
-     *        http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html mentions "such that all of the
-     *        fields of Helper are final".
-     *
-     *        There are only two violations, both relating to dynamic discovery of augmentations, which should be
-     *        eliminated once we have constant class loader visibility (and thus can discover all valid augmentations
-     *        and aliases). Alternatively, adding an indirection could be considered after the effects are fully
-     *        understood.
-     *
-     *        Alternatively we can consider the use of VarHandle acquire/release mechanics to remove this instance
-     *        from global synchronization order -- that would be a Java 9+ concern, though.
-     *
-     *        Finally, the benefits of addressing this are masked by CodecDataObject operating on volatiles, hence
-     *        already doing a barrier very close to us -- thus reducing the scope in which code can be reordered.
-     *        If CodecDataObject moves to VarHandles and uses acquire/release there, we will be put on the spot here,
-     *        though.
-     */
+    // Accessed via INSTANCE
+    @SuppressWarnings("unused")
     private volatile DataContainerCodecContext<?, T> instance;
 
     @SuppressWarnings("unchecked")
@@ -147,7 +132,7 @@ final class DataContainerCodecPrototype<T extends WithStatus> implements NodeCon
         } else if (nodeSchema instanceof ChoiceSchemaNode) {
             boolean haveAddressable = false;
             boolean haveUnaddressable = false;
-            for (CaseSchemaNode child : ((ChoiceSchemaNode) nodeSchema).getCases().values()) {
+            for (CaseSchemaNode child : ((ChoiceSchemaNode) nodeSchema).getCases()) {
                 switch (computeChildAddressabilitySummary(child)) {
                     case ADDRESSABLE:
                         haveAddressable = true;
@@ -175,28 +160,25 @@ final class DataContainerCodecPrototype<T extends WithStatus> implements NodeCon
         return ChildAddressabilitySummary.UNADDRESSABLE;
     }
 
-    static DataContainerCodecPrototype<SchemaContext> rootPrototype(final CodecContextFactory factory) {
-        final SchemaContext schema = factory.getRuntimeContext().getSchemaContext();
-        final NodeIdentifier arg = NodeIdentifier.create(schema.getQName());
+    static DataContainerCodecPrototype<EffectiveModelContext> rootPrototype(final CodecContextFactory factory) {
+        final EffectiveModelContext schema = factory.getRuntimeContext().getEffectiveModelContext();
+        final NodeIdentifier arg = NodeIdentifier.create(SchemaContext.NAME);
         return new DataContainerCodecPrototype<>(DataRoot.class, arg, schema, factory);
     }
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
     static <T extends DataSchemaNode> DataContainerCodecPrototype<T> from(final Class<?> cls, final T schema,
             final CodecContextFactory factory) {
-        return new DataContainerCodecPrototype(cls, NodeIdentifier.create(schema.getQName()), schema, factory);
+        return new DataContainerCodecPrototype<>(cls, NodeIdentifier.create(schema.getQName()), schema, factory);
     }
 
-    @SuppressWarnings({ "unchecked", "rawtypes" })
     static <T extends DataSchemaNode> DataContainerCodecPrototype<T> from(final Item<?> bindingArg, final T schema,
             final CodecContextFactory factory) {
-        return new DataContainerCodecPrototype(bindingArg, NodeIdentifier.create(schema.getQName()), schema, factory);
+        return new DataContainerCodecPrototype<>(bindingArg, NodeIdentifier.create(schema.getQName()), schema, factory);
     }
 
-    @SuppressWarnings({ "rawtypes", "unchecked" })
-    static DataContainerCodecPrototype<?> from(final Class<?> augClass, final AugmentationIdentifier arg,
-            final AugmentationSchemaNode schema, final CodecContextFactory factory) {
-        return new DataContainerCodecPrototype(augClass, arg, schema, factory);
+    static DataContainerCodecPrototype<AugmentationSchemaNode> from(final Class<?> augClass,
+            final AugmentationIdentifier arg, final AugmentationSchemaNode schema, final CodecContextFactory factory) {
+        return new DataContainerCodecPrototype<>(augClass, arg, schema, factory);
     }
 
     static DataContainerCodecPrototype<NotificationDefinition> from(final Class<?> augClass,
@@ -205,7 +187,7 @@ final class DataContainerCodecPrototype<T extends WithStatus> implements NodeCon
         return new DataContainerCodecPrototype<>(augClass,arg, schema, factory);
     }
 
-    protected T getSchema() {
+    T getSchema() {
         return schema;
     }
 
@@ -213,46 +195,43 @@ final class DataContainerCodecPrototype<T extends WithStatus> implements NodeCon
         return childAddressabilitySummary;
     }
 
-    protected QNameModule getNamespace() {
+    QNameModule getNamespace() {
         return namespace;
     }
 
-    protected CodecContextFactory getFactory() {
+    CodecContextFactory getFactory() {
         return factory;
     }
 
-    protected Class<?> getBindingClass() {
+    Class<?> getBindingClass() {
         return bindingArg.getType();
     }
 
-    protected Item<?> getBindingArg() {
+    Item<?> getBindingArg() {
         return bindingArg;
     }
 
-    protected PathArgument getYangArg() {
+    PathArgument getYangArg() {
         return yangArg;
     }
 
     @Override
     public DataContainerCodecContext<?, T> get() {
-        DataContainerCodecContext<?, T> tmp = instance;
-        if (tmp == null) {
-            synchronized (this) {
-                tmp = instance;
-                if (tmp == null) {
-                    instance = tmp = createInstance();
-                }
-            }
-        }
+        final DataContainerCodecContext<?, T> existing = (DataContainerCodecContext<?, T>) INSTANCE.getAcquire(this);
+        return existing != null ? existing : loadInstance();
+    }
 
-        return tmp;
+    private @NonNull DataContainerCodecContext<?, T> loadInstance() {
+        final var tmp = createInstance();
+        final var witness = (DataContainerCodecContext<?, T>) INSTANCE.compareAndExchangeRelease(this, null, tmp);
+        return witness == null ? tmp : witness;
     }
 
-    @Holding("this")
     @SuppressWarnings({ "rawtypes", "unchecked" })
+    // This method must allow concurrent loading, i.e. nothing in it may have effects outside of the loaded object
     private @NonNull DataContainerCodecContext<?, T> createInstance() {
         // FIXME: make protected abstract
-        if (schema instanceof ContainerSchemaNode) {
+        if (schema instanceof ContainerLike) {
             return new ContainerNodeCodecContext(this);
         } else if (schema instanceof ListSchemaNode) {
             return Identifiable.class.isAssignableFrom(getBindingClass())
@@ -268,6 +247,7 @@ final class DataContainerCodecPrototype<T extends WithStatus> implements NodeCon
         throw new IllegalArgumentException("Unsupported type " + getBindingClass() + " " + schema);
     }
 
+    // FIXME: eliminate with above createInstance() item
     boolean isChoice() {
         return schema instanceof ChoiceSchemaNode;
     }