BUG-1886: implement lock-free InMemoryDataTree.commit() 40/11240/1
authorRobert Varga <rovarga@cisco.com>
Tue, 16 Sep 2014 13:31:30 +0000 (15:31 +0200)
committerRobert Varga <rovarga@cisco.com>
Tue, 16 Sep 2014 14:09:47 +0000 (16:09 +0200)
This patch reworks the data layout to remove all locking from the hold
codepaths. All state is not encapsulated in DataTreeState which is
atomically replaced.

The allocation, verification and preparation of a modification are
both lock- and wait-free.

Schema context is still synchronized to preventmadness
from concurrent schema context changes.

Commit runs lock-free, but may retry operations if it races with schema
context change -- which should happen rarely if ever.

Change-Id: I296b2805ef2575e76052e004a0d317a098d207df
Signed-off-by: Robert Varga <rovarga@cisco.com>
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/DataTreeState.java [new file with mode: 0644]
yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/InMemoryDataTree.java

diff --git a/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/DataTreeState.java b/yang/yang-data-impl/src/main/java/org/opendaylight/yangtools/yang/data/impl/schema/tree/DataTreeState.java
new file mode 100644 (file)
index 0000000..db1a8d1
--- /dev/null
@@ -0,0 +1,57 @@
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. 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.tree;
+
+import com.google.common.base.Preconditions;
+import org.opendaylight.yangtools.yang.data.api.schema.tree.spi.TreeNode;
+import org.opendaylight.yangtools.yang.model.api.SchemaContext;
+
+/**
+ * Instances of this class hold the current state of a DataTree instance.
+ * The need for encapsulation stems from atomic updates, which potentially change
+ * multiple fields in one go.
+ */
+final class DataTreeState {
+    private final LatestOperationHolder holder;
+    private final SchemaContext schemaContext;
+    private final TreeNode root;
+
+    private DataTreeState(final TreeNode root) {
+        this.root = Preconditions.checkNotNull(root);
+        holder = new LatestOperationHolder();
+        schemaContext = null;
+    }
+
+    private DataTreeState(final TreeNode root, final LatestOperationHolder holder, final SchemaContext schemaContext) {
+        // It should be impossible to instantiate a new root without a SchemaContext
+        this.schemaContext = Preconditions.checkNotNull(schemaContext);
+        this.holder = Preconditions.checkNotNull(holder);
+        this.root = Preconditions.checkNotNull(root);
+    }
+
+    static DataTreeState createInitial(final TreeNode root) {
+        return new DataTreeState(root);
+    }
+
+    TreeNode getRoot() {
+        return root;
+    }
+
+    InMemoryDataTreeSnapshot newSnapshot() {
+        return new InMemoryDataTreeSnapshot(schemaContext, root, holder.newSnapshot());
+    }
+
+    DataTreeState withSchemaContext(final SchemaContext newSchemaContext, final SchemaAwareApplyOperation operation) {
+        holder.setCurrent(operation);
+        return new DataTreeState(root, holder, newSchemaContext);
+    }
+
+    DataTreeState withRoot(final TreeNode newRoot) {
+        return new DataTreeState(newRoot, holder, schemaContext);
+    }
+}
\ No newline at end of file
index 2dd71bc0c93b2a269255ef4fad87db31a742e608..dd1f00ed5e21a67cba5706e303ba90c43322c7df 100644 (file)
@@ -9,9 +9,10 @@ package org.opendaylight.yangtools.yang.data.impl.schema.tree;
 
 import com.google.common.base.Optional;
 import com.google.common.base.Preconditions;
-import java.util.concurrent.locks.ReadWriteLock;
-import java.util.concurrent.locks.ReentrantReadWriteLock;
+import java.util.Collections;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.PathArgument;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTree;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
@@ -26,56 +27,46 @@ import org.slf4j.LoggerFactory;
  * Read-only snapshot of the data tree.
  */
 final class InMemoryDataTree implements DataTree {
+    private static final YangInstanceIdentifier PUBLIC_ROOT_PATH = YangInstanceIdentifier.create(Collections.<PathArgument>emptyList());
+    private static final AtomicReferenceFieldUpdater<InMemoryDataTree, DataTreeState> STATE_UPDATER =
+            AtomicReferenceFieldUpdater.newUpdater(InMemoryDataTree.class, DataTreeState.class, "state");
     private static final Logger LOG = LoggerFactory.getLogger(InMemoryDataTree.class);
-    private static final YangInstanceIdentifier PUBLIC_ROOT_PATH = YangInstanceIdentifier.builder().build();
 
-    private final ReadWriteLock rwLock = new ReentrantReadWriteLock(true);
-    private final LatestOperationHolder operationHolder = new LatestOperationHolder();
-    private SchemaContext currentSchemaContext;
-    private TreeNode rootNode;
+    /**
+     * Current data store state generation.
+     */
+    private volatile DataTreeState state;
 
     public InMemoryDataTree(final TreeNode rootNode, final SchemaContext schemaContext) {
-        this.rootNode = Preconditions.checkNotNull(rootNode);
-
+        state = DataTreeState.createInitial(rootNode);
         if (schemaContext != null) {
-            // Also sets applyOper
             setSchemaContext(schemaContext);
         }
     }
 
+    /*
+     * This method is synchronized to guard against user attempting to install
+     * multiple contexts. Otherwise it runs in a lock-free manner.
+     */
     @Override
-    public void setSchemaContext(final SchemaContext newSchemaContext) {
+    public synchronized void setSchemaContext(final SchemaContext newSchemaContext) {
         Preconditions.checkNotNull(newSchemaContext);
 
         LOG.info("Attempting to install schema contexts");
-        LOG.debug("Following schema contexts will be attempted {}",newSchemaContext);
-
-        /*
-         * FIXME: we should walk the schema contexts, both current and new and see
-         *        whether they are compatible here. Reject incompatible changes.
-         */
-
-        // Instantiate new apply operation, this still may fail
-        final ModificationApplyOperation newApplyOper = SchemaAwareApplyOperation.from(newSchemaContext);
-
-        // Ready to change the context now, make sure no operations are running
-        rwLock.writeLock().lock();
-        try {
-            this.operationHolder.setCurrent(newApplyOper);
-            this.currentSchemaContext = newSchemaContext;
-        } finally {
-            rwLock.writeLock().unlock();
-        }
+        LOG.debug("Following schema contexts will be attempted {}", newSchemaContext);
+
+        final SchemaAwareApplyOperation operation = SchemaAwareApplyOperation.from(newSchemaContext);
+
+        DataTreeState currentState, newState;
+        do {
+            currentState = state;
+            newState = currentState.withSchemaContext(newSchemaContext, operation);
+        } while (!STATE_UPDATER.compareAndSet(this, currentState, newState));
     }
 
     @Override
     public InMemoryDataTreeSnapshot takeSnapshot() {
-        rwLock.readLock().lock();
-        try {
-            return new InMemoryDataTreeSnapshot(currentSchemaContext, rootNode, operationHolder.newSnapshot());
-        } finally {
-            rwLock.readLock().unlock();
-        }
+        return state.newSnapshot();
     }
 
     @Override
@@ -83,12 +74,7 @@ final class InMemoryDataTree implements DataTree {
         Preconditions.checkArgument(modification instanceof InMemoryDataTreeModification, "Invalid modification class %s", modification.getClass());
         final InMemoryDataTreeModification m = (InMemoryDataTreeModification)modification;
 
-        rwLock.readLock().lock();
-        try {
-            m.getStrategy().checkApplicable(PUBLIC_ROOT_PATH, m.getRootModification(), Optional.<TreeNode>of(rootNode));
-        } finally {
-            rwLock.readLock().unlock();
-        }
+        m.getStrategy().checkApplicable(PUBLIC_ROOT_PATH, m.getRootModification(), Optional.<TreeNode>of(state.getRoot()));
     }
 
     @Override
@@ -102,15 +88,11 @@ final class InMemoryDataTree implements DataTree {
             return new NoopDataTreeCandidate(PUBLIC_ROOT_PATH, root);
         }
 
-        rwLock.writeLock().lock();
-        try {
-            final Optional<TreeNode> newRoot = m.getStrategy().apply(m.getRootModification(),
-                Optional.<TreeNode>of(rootNode), m.getVersion());
-            Preconditions.checkState(newRoot.isPresent(), "Apply strategy failed to produce root node");
-            return new InMemoryDataTreeCandidate(PUBLIC_ROOT_PATH, root, rootNode, newRoot.get());
-        } finally {
-            rwLock.writeLock().unlock();
-        }
+        final TreeNode currentRoot = state.getRoot();
+        final Optional<TreeNode> newRoot = m.getStrategy().apply(m.getRootModification(),
+            Optional.<TreeNode>of(currentRoot), m.getVersion());
+        Preconditions.checkState(newRoot.isPresent(), "Apply strategy failed to produce root node");
+        return new InMemoryDataTreeCandidate(PUBLIC_ROOT_PATH, root, currentRoot, newRoot.get());
     }
 
     @Override
@@ -122,20 +104,22 @@ final class InMemoryDataTree implements DataTree {
         Preconditions.checkArgument(candidate instanceof InMemoryDataTreeCandidate, "Invalid candidate class %s", candidate.getClass());
         final InMemoryDataTreeCandidate c = (InMemoryDataTreeCandidate)candidate;
 
-        LOG.debug("Updating datastore from {} to {}", rootNode, c.getAfterRoot());
-
         if (LOG.isTraceEnabled()) {
             LOG.trace("Data Tree is {}", StoreUtils.toStringTree(c.getAfterRoot().getData()));
         }
 
-        // Ready to change the context now, make sure no operations are running
-        rwLock.writeLock().lock();
-        try {
-            Preconditions.checkState(c.getBeforeRoot() == rootNode,
-                    "Store tree %s and candidate base %s differ.", rootNode, c.getBeforeRoot());
-            this.rootNode = c.getAfterRoot();
-        } finally {
-            rwLock.writeLock().unlock();
-        }
+        final TreeNode newRoot = c.getAfterRoot();
+        DataTreeState currentState, newState;
+        do {
+            currentState = state;
+            final TreeNode currentRoot = currentState.getRoot();
+            LOG.debug("Updating datastore from {} to {}", currentRoot, newRoot);
+
+            final TreeNode oldRoot = c.getBeforeRoot();
+            Preconditions.checkState(oldRoot == currentRoot, "Store tree %s and candidate base %s differ.", currentRoot, oldRoot);
+
+            newState = currentState.withRoot(newRoot);
+            LOG.trace("Updated state from {} to {}", currentState, newState);
+        } while (!STATE_UPDATER.compareAndSet(this, currentState, newState));
     }
 }