Add batching of non-isolated transaction in ShardedDOMDataTreeProducer 29/45829/1
authorTomas Cere <tcere@cisco.com>
Fri, 9 Sep 2016 09:43:02 +0000 (11:43 +0200)
committerRobert Varga <rovarga@cisco.com>
Mon, 19 Sep 2016 15:07:18 +0000 (17:07 +0200)
Change-Id: If5ee94a6b2f58c6fd5a243f8784c8b56200b7343
Signed-off-by: Tomas Cere <tcere@cisco.com>
(cherry picked from commit 1a1d9e054b9676dfe386241bc62d02c877fcff50)

dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeProducer.java
dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeWriteTransaction.java
dom/mdsal-dom-broker/src/test/java/org/opendaylight/mdsal/dom/broker/ShardedDOMDataTreeTest.java
dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InMemoryDOMDataTreeShardProducer.java
dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/InmemoryDOMDataTreeShardWriteTransaction.java
dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/ShardDataModification.java
dom/mdsal-dom-inmemory-datastore/src/main/java/org/opendaylight/mdsal/dom/store/inmemory/ShardRootModificationContext.java
dom/mdsal-dom-inmemory-datastore/src/test/java/org/opendaylight/mdsal/dom/store/inmemory/InmemoryDOMDataTreeShardWriteTransactionTest.java

index 029721b1a3caed8e036450e5923342bd19a958f8..837871c03156addd19e4d5dac364fe38ff794ac8 100644 (file)
@@ -8,6 +8,7 @@
 package org.opendaylight.mdsal.dom.broker;
 
 import com.google.common.base.Preconditions;
+import com.google.common.base.Verify;
 import com.google.common.collect.ArrayListMultimap;
 import com.google.common.collect.BiMap;
 import com.google.common.collect.ImmutableBiMap;
@@ -20,6 +21,7 @@ import java.util.Collections;
 import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
+import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 import javax.annotation.concurrent.GuardedBy;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
@@ -34,14 +36,29 @@ import org.slf4j.LoggerFactory;
 
 class ShardedDOMDataTreeProducer implements DOMDataTreeProducer {
     private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTreeProducer.class);
+
     private final Set<DOMDataTreeIdentifier> subtrees;
     private final ShardedDOMDataTree dataTree;
 
     private BiMap<DOMDataTreeIdentifier, DOMDataTreeShardProducer> idToProducer = ImmutableBiMap.of();
     private Map<DOMDataTreeIdentifier, DOMDataTreeShard> idToShard;
 
-    @GuardedBy("this")
-    private DOMDataTreeCursorAwareTransaction openTx;
+    private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
+        CURRENT_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
+            ShardedDOMDataTreeWriteTransaction.class, "currentTx");
+    @SuppressWarnings("unused")
+    private volatile ShardedDOMDataTreeWriteTransaction currentTx;
+
+    private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
+        OPEN_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
+            ShardedDOMDataTreeWriteTransaction.class, "openTx");
+    private volatile ShardedDOMDataTreeWriteTransaction openTx;
+
+    private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
+        LAST_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
+            ShardedDOMDataTreeWriteTransaction.class, "lastTx");
+    private volatile ShardedDOMDataTreeWriteTransaction lastTx;
+
     @GuardedBy("this")
     private Map<DOMDataTreeIdentifier, DOMDataTreeProducer> children = Collections.emptyMap();
     @GuardedBy("this")
@@ -85,8 +102,8 @@ class ShardedDOMDataTreeProducer implements DOMDataTreeProducer {
         idToShard = ImmutableMap.copyOf(shardMap);
     }
 
-    private BiMap<DOMDataTreeIdentifier, DOMDataTreeShardProducer> mapIdsToProducer(final Multimap<DOMDataTreeShard,
-            DOMDataTreeIdentifier> shardToId) {
+    private static BiMap<DOMDataTreeIdentifier, DOMDataTreeShardProducer> mapIdsToProducer(
+            final Multimap<DOMDataTreeShard, DOMDataTreeIdentifier> shardToId) {
         final Builder<DOMDataTreeIdentifier, DOMDataTreeShardProducer> idToProducerBuilder = ImmutableBiMap.builder();
         for (final Entry<DOMDataTreeShard, Collection<DOMDataTreeIdentifier>> entry : shardToId.asMap().entrySet()) {
             if (entry.getKey() instanceof WriteableDOMDataTreeShard) {
@@ -110,9 +127,34 @@ class ShardedDOMDataTreeProducer implements DOMDataTreeProducer {
         Preconditions.checkState(!closed, "Producer is already closed");
         Preconditions.checkState(openTx == null, "Transaction %s is still open", openTx);
 
-        this.openTx = new ShardedDOMDataTreeWriteTransaction(this, idToProducer, children);
+        LOG.debug("Creating transaction from producer");
+        final ShardedDOMDataTreeWriteTransaction current = CURRENT_UPDATER.getAndSet(this, null);
+        final ShardedDOMDataTreeWriteTransaction ret;
+        if (isolated) {
+            // Isolated case. If we have a previous transaction, submit it before returning this one.
+            if (current != null) {
+                submitTransaction(current);
+            }
+            ret = new ShardedDOMDataTreeWriteTransaction(this, idToProducer, children, true);
+        } else {
+            // Non-isolated case, see if we can reuse the transaction
+            if (current != null) {
+                LOG.debug("Reusing previous transaction {} since there is still a transaction inflight",
+                        current.getIdentifier());
+                ret = current;
+            } else {
+                ret = new ShardedDOMDataTreeWriteTransaction(this, idToProducer, children, false);
+            }
+        }
+
+        final boolean success = OPEN_UPDATER.compareAndSet(this, null, ret);
+        Verify.verify(success);
+        return ret;
+    }
 
-        return openTx;
+    private void submitTransaction(final ShardedDOMDataTreeWriteTransaction current) {
+        lastTx = current;
+        current.doSubmit(this::transactionSuccessful, this::transactionFailed);
     }
 
     @GuardedBy("this")
@@ -195,25 +237,62 @@ class ShardedDOMDataTreeProducer implements DOMDataTreeProducer {
         return subtrees;
     }
 
-    synchronized void cancelTransaction(final ShardedDOMDataTreeWriteTransaction transaction) {
-        if (!openTx.equals(transaction)) {
+    void cancelTransaction(final ShardedDOMDataTreeWriteTransaction transaction) {
+        final boolean success = OPEN_UPDATER.compareAndSet(this, transaction, null);
+        if (success) {
+            LOG.debug("Transaction {} cancelled", transaction);
+        } else {
             LOG.warn("Transaction {} is not open in producer {}", transaction, this);
-            return;
         }
+    }
+
+    void processTransaction(final ShardedDOMDataTreeWriteTransaction transaction) {
+        final boolean wasOpen = OPEN_UPDATER.compareAndSet(this, transaction, null);
+        Verify.verify(wasOpen);
+
+        if (lastTx != null) {
+            final boolean success = CURRENT_UPDATER.compareAndSet(this, null, transaction);
+            Verify.verify(success);
+            if (lastTx == null) {
+                // Dispatch after requeue
+                processCurrentTransaction();
+            }
+        } else {
+            submitTransaction(transaction);
+        }
+    }
+
+    void transactionSuccessful(final ShardedDOMDataTreeWriteTransaction tx, final Void result) {
+        LOG.debug("Transaction {} completed successfully", tx.getIdentifier());
 
-        LOG.debug("Transaction {} cancelled", transaction);
-        openTx = null;
+        tx.onTransactionSuccess(result);
+        processNextTransaction(tx);
     }
 
-    synchronized void transactionSubmitted(final ShardedDOMDataTreeWriteTransaction transaction) {
-        Preconditions.checkState(openTx.equals(transaction));
-        openTx = null;
+    void transactionFailed(final ShardedDOMDataTreeWriteTransaction tx, final Throwable throwable) {
+        LOG.debug("Transaction {} failed", tx.getIdentifier(), throwable);
+
+        tx.onTransactionFailure(throwable);
+        processNextTransaction(tx);
+    }
+
+    private void processCurrentTransaction() {
+        final ShardedDOMDataTreeWriteTransaction current = CURRENT_UPDATER.getAndSet(this, null);
+        if (current != null) {
+            submitTransaction(current);
+        }
+    }
+
+    private void processNextTransaction(final ShardedDOMDataTreeWriteTransaction tx) {
+        final boolean wasLast = LAST_UPDATER.compareAndSet(this, tx, null);
+        if (wasLast) {
+            processCurrentTransaction();
+        }
     }
 
     synchronized void boundToListener(final ShardedDOMDataTreeListenerContext<?> listener) {
-        // FIXME: Add option to dettach
-        Preconditions.checkState(this.attachedListener == null,
-                "Producer %s is already attached to other listener.",
+        // FIXME: Add option to detach
+        Preconditions.checkState(this.attachedListener == null, "Producer %s is already attached to other listener.",
                 listener.getListener());
         this.attachedListener = listener;
     }
index fb5c1315fecfa15cebe80ad357fbe052dd7e639d..24b640501f01cc0bc48d28483070f48e0b384a42 100644 (file)
@@ -23,6 +23,7 @@ import java.util.Map;
 import java.util.Map.Entry;
 import java.util.Set;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.function.BiConsumer;
 import java.util.stream.Collectors;
 import javax.annotation.Nonnull;
 import javax.annotation.concurrent.GuardedBy;
@@ -54,14 +55,23 @@ final class ShardedDOMDataTreeWriteTransaction implements DOMDataTreeCursorAware
     @GuardedBy("this")
     private DOMDataTreeWriteCursor openCursor;
 
+    private final SettableFuture<Void> future = SettableFuture.create();
+    private final CheckedFuture<Void, TransactionCommitFailedException> submitFuture =
+            Futures.makeChecked(future, TransactionCommitFailedExceptionMapper.create("submit"));
+
+    private final boolean isolated;
+
     ShardedDOMDataTreeWriteTransaction(final ShardedDOMDataTreeProducer producer,
                                        final Map<DOMDataTreeIdentifier, DOMDataTreeShardProducer> idToProducer,
-                                       final Map<DOMDataTreeIdentifier, DOMDataTreeProducer> childProducers) {
+                                       final Map<DOMDataTreeIdentifier, DOMDataTreeProducer> childProducers,
+                                       final boolean isolated) {
+        this.isolated = isolated;
         this.producer = Preconditions.checkNotNull(producer);
         idToTransaction = new HashMap<>();
         Preconditions.checkNotNull(idToProducer).forEach((id, prod) -> idToTransaction.put(
                 id, prod.createTransaction()));
         this.identifier = "SHARDED-DOM-" + COUNTER.getAndIncrement();
+        LOG.debug("Created new transaction{}", identifier);
         childProducers.forEach((id, prod) -> childBoundaries.add(id.getRootIdentifier()));
     }
 
@@ -118,9 +128,18 @@ final class ShardedDOMDataTreeWriteTransaction implements DOMDataTreeCursorAware
         Preconditions.checkState(!closed, "Transaction %s is already closed", identifier);
         Preconditions.checkState(openCursor == null, "Cannot submit transaction while there is a cursor open");
 
+        producer.processTransaction(this);
+        return submitFuture;
+    }
+
+    CheckedFuture<Void, TransactionCommitFailedException> doSubmit(
+            BiConsumer<ShardedDOMDataTreeWriteTransaction, Void> success,
+            BiConsumer<ShardedDOMDataTreeWriteTransaction, Throwable> failure) {
+
         final Set<DOMDataTreeShardWriteTransaction> txns = ImmutableSet.copyOf(idToTransaction.values());
         final ListenableFuture<List<Void>> listListenableFuture =
                 Futures.allAsList(txns.stream().map(tx -> {
+                    LOG.debug("Readying tx {}", identifier);
                     tx.ready();
                     return tx.submit();
                 }).collect(Collectors.toList()));
@@ -130,29 +149,44 @@ final class ShardedDOMDataTreeWriteTransaction implements DOMDataTreeCursorAware
             @Override
             public void onSuccess(final List<Void> result) {
                 ret.set(null);
+                success.accept(ShardedDOMDataTreeWriteTransaction.this, null);
             }
 
             @Override
             public void onFailure(final Throwable exp) {
                 ret.setException(exp);
+                failure.accept(ShardedDOMDataTreeWriteTransaction.this, exp);
             }
         });
 
-        producer.transactionSubmitted(this);
         return Futures.makeChecked(ret, TransactionCommitFailedExceptionMapper.create("submit"));
     }
 
+    void onTransactionSuccess(final Void result) {
+        future.set(result);
+    }
+
+    void onTransactionFailure(final Throwable throwable) {
+        future.setException(throwable);
+    }
+
     synchronized void cursorClosed() {
         openCursor = null;
     }
 
+    boolean isIsolated() {
+        return isolated;
+    }
+
     private class DelegatingCursor implements DOMDataTreeWriteCursor {
 
         private final DOMDataTreeWriteCursor delegate;
+        private final DOMDataTreeIdentifier rootPosition;
         private final Deque<PathArgument> path = new LinkedList<>();
 
         DelegatingCursor(final DOMDataTreeWriteCursor delegate, final DOMDataTreeIdentifier rootPosition) {
-            this.delegate = delegate;
+            this.delegate = Preconditions.checkNotNull(delegate);
+            this.rootPosition = Preconditions.checkNotNull(rootPosition);
             path.addAll(rootPosition.getRootIdentifier().getPathArguments());
         }
 
@@ -193,6 +227,12 @@ final class ShardedDOMDataTreeWriteTransaction implements DOMDataTreeCursorAware
 
         @Override
         public void close() {
+            int depthEntered = path.size() - rootPosition.getRootIdentifier().getPathArguments().size();
+            if (depthEntered > 0) {
+                // clean up existing modification cursor in case this tx will be reused for batching
+                delegate.exit(depthEntered);
+            }
+
             delegate.close();
             cursorClosed();
         }
index 35611e94a6ce9e94b21d5b899754ed669ea31fcf..058769544c0c0ed951192f904973af188ddbf4ed 100644 (file)
@@ -18,6 +18,8 @@ import static org.mockito.Mockito.timeout;
 import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.verifyNoMoreInteractions;
 
+import com.google.common.util.concurrent.CheckedFuture;
+import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -31,6 +33,7 @@ import org.mockito.Captor;
 import org.mockito.Mockito;
 import org.mockito.MockitoAnnotations;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.TransactionCommitFailedException;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeListener;
@@ -39,14 +42,20 @@ import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor;
 import org.opendaylight.mdsal.dom.broker.util.TestModel;
 import org.opendaylight.mdsal.dom.store.inmemory.InMemoryDOMDataTreeShard;
 import org.opendaylight.yangtools.concepts.ListenerRegistration;
+import org.opendaylight.yangtools.yang.common.QName;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifier;
+import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier.NodeIdentifierWithPredicates;
 import org.opendaylight.yangtools.yang.data.api.schema.ContainerNode;
 import org.opendaylight.yangtools.yang.data.api.schema.LeafNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapEntryNode;
+import org.opendaylight.yangtools.yang.data.api.schema.MapNode;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeCandidate;
+import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableContainerNodeBuilder;
 import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableLeafNodeBuilder;
+import org.opendaylight.yangtools.yang.data.impl.schema.builder.impl.ImmutableMapNodeBuilder;
 import org.opendaylight.yangtools.yang.model.api.SchemaContext;
 import org.opendaylight.yangtools.yang.parser.spi.meta.ReactorException;
 import org.slf4j.Logger;
@@ -190,6 +199,67 @@ public class ShardedDOMDataTreeTest {
         verifyNoMoreInteractions(mockedDataTreeListener);
     }
 
+    @Test
+    public void testMultipleWritesIntoSingleMapEntry() throws Exception {
+
+        final YangInstanceIdentifier oid1 = TestModel.OUTER_LIST_PATH.node(new NodeIdentifierWithPredicates(
+                TestModel.OUTER_LIST_QNAME, QName.create(TestModel.OUTER_LIST_QNAME, "id"), 0));
+        final DOMDataTreeIdentifier outerListPath = new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, oid1);
+
+        final DOMDataTreeProducer shardProducer = dataTreeService.createProducer(
+                Collections.singletonList(outerListPath));
+        final InMemoryDOMDataTreeShard outerListShard = InMemoryDOMDataTreeShard.create(outerListPath, executor, 1000);
+        outerListShard.onGlobalContextUpdated(schemaContext);
+
+        final ListenerRegistration<InMemoryDOMDataTreeShard> oid1ShardRegistration =
+                dataTreeService.registerDataTreeShard(outerListPath, outerListShard, shardProducer);
+
+        final DOMDataTreeCursorAwareTransaction tx = shardProducer.createTransaction(false);
+        final DOMDataTreeWriteCursor cursor =
+                tx.createCursor(new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION, oid1));
+        assertNotNull(cursor);
+
+        MapNode innerList = ImmutableMapNodeBuilder
+                .create()
+                .withNodeIdentifier(new NodeIdentifier(TestModel.INNER_LIST_QNAME))
+                .build();
+
+        cursor.write(new NodeIdentifier(TestModel.INNER_LIST_QNAME), innerList);
+        cursor.close();
+        tx.submit().checkedGet();
+
+        final ArrayList<CheckedFuture<Void, TransactionCommitFailedException>> futures = new ArrayList<>();
+        final Collection<MapEntryNode> innerListMapEntries = createInnerListMapEntries(1000, "run-1");
+        for (final MapEntryNode innerListMapEntry : innerListMapEntries) {
+            final DOMDataTreeCursorAwareTransaction tx1 = shardProducer.createTransaction(false);
+            final DOMDataTreeWriteCursor cursor1 = tx1.createCursor(
+                    new DOMDataTreeIdentifier(LogicalDatastoreType.CONFIGURATION,
+                            oid1.node(new NodeIdentifier(TestModel.INNER_LIST_QNAME))));
+            cursor1.write(innerListMapEntry.getIdentifier(), innerListMapEntry);
+            cursor1.close();
+            futures.add(tx1.submit());
+        }
+
+        futures.get(futures.size() - 1).checkedGet();
+
+    }
+
+    private Collection<MapEntryNode> createInnerListMapEntries(int amount, String valuePrefix) {
+        final Collection<MapEntryNode> ret = new ArrayList<>();
+        for (int i = 0; i < amount; i++) {
+            ret.add(ImmutableNodes.mapEntryBuilder()
+                    .withNodeIdentifier(new NodeIdentifierWithPredicates(TestModel.INNER_LIST_QNAME,
+                            QName.create(TestModel.OUTER_LIST_QNAME, "name"), Integer.toString(i)))
+                    .withChild(ImmutableNodes
+                            .leafNode(QName.create(TestModel.INNER_LIST_QNAME, "name"), Integer.toString(i)))
+                    .withChild(ImmutableNodes
+                            .leafNode(QName.create(TestModel.INNER_LIST_QNAME, "value"), valuePrefix + "-" + i))
+                    .build());
+        }
+
+        return ret;
+    }
+
     @Test
     public void testMultipleProducerCursorCreation() throws Exception {
 
index 0d11ac6e25ead1c82f12b7c44cb282d507cca63d..6c0d2775c6c694299ed21edad103b6a5866f7456 100644 (file)
@@ -29,7 +29,7 @@ final class InMemoryDOMDataTreeShardProducer implements DOMDataTreeShardProducer
 
     @Override
     public InmemoryDOMDataTreeShardWriteTransaction createTransaction() {
-        Preconditions.checkState(currentTx == null || currentTx.isFinished(), "Previous transaction not finished yet.");
+//      Preconditions.checkState(currentTx == null || currentTx.isFinished(), "Previous transaction not finished yet.");
         if (lastSubmittedTx != null) {
             currentTx = parentShard.createTransaction(lastSubmittedTx);
         } else {
index f04641533d14fec8599ee17417e35c96ae5cc20a..2ef7ebb28bbc5da9e1eb220362f5ff639b0e1c5a 100644 (file)
@@ -144,6 +144,7 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT
 
     void cursorClosed() {
         Preconditions.checkNotNull(cursor);
+        modification.closeCursor();
         cursor = null;
     }
 
@@ -209,10 +210,6 @@ class InmemoryDOMDataTreeShardWriteTransaction implements DOMDataTreeShardWriteT
         return submit;
     }
 
-    public void followUp() {
-
-    }
-
     @Override
     public DOMDataTreeWriteCursor createCursor(final DOMDataTreeIdentifier prefix) {
         Preconditions.checkState(!finished, "Transaction is finished/closed already.");
index a2e169a2b5170868cae332a7420736c84ff5e971..eeb8e91d6611343d2d5215a5c596871aa5128aee 100644 (file)
@@ -79,4 +79,7 @@ final class ShardDataModification extends WriteableNodeWithSubshard {
         }
     }
 
+    void closeCursor() {
+        rootContext.closeCursor();
+    }
 }
\ No newline at end of file
index 8aea26da68605a171a8d877e688156834694644f..41bd1cef9933c621a0d73f1b6b7121a842790b67 100644 (file)
@@ -65,4 +65,9 @@ class ShardRootModificationContext {
 
         return ret;
     }
+
+    void closeCursor() {
+        cursor.close();
+        cursor = null;
+    }
 }
index 1a7a0cf32445d4822c01755f035ccf0d20a37dd9..7d657e62aa31863bc0cd82255867112c7eacd76e 100644 (file)
@@ -70,6 +70,7 @@ public class InmemoryDOMDataTreeShardWriteTransactionTest {
         final DataTreeModificationCursor dataTreeModificationCursor = mock(DataTreeModificationCursor.class);
         doReturn(DataTreeModificationCursorAdaptor.of( dataTreeModificationCursor))
                 .when(SHARD_ROOT_MODIFICATION_CONTEXT).cursor();
+        doNothing().when(SHARD_ROOT_MODIFICATION_CONTEXT).closeCursor();
         final DataTreeCandidate dataTreeCandidate = mock(DataTreeCandidate.class);
         final DataTreeCandidateNode dataTreeCandidateNode = mock(DataTreeCandidateNode.class);
         doReturn(dataTreeCandidateNode).when(dataTreeCandidate).getRootNode();