Do not allow multi-datastore transactions
[controller.git] / opendaylight / md-sal / sal-distributed-datastore / src / main / java / org / opendaylight / controller / cluster / databroker / AbstractDOMBrokerTransaction.java
index 2655b61f682e526a6205db89b9fe1da10e1821f6..f75707ccbf4082876e4802cc81a5a9abaa720f9c 100644 (file)
@@ -12,22 +12,37 @@ import static java.util.Objects.requireNonNull;
 
 import com.google.common.base.MoreObjects;
 import com.google.common.base.MoreObjects.ToStringHelper;
-import java.util.Collection;
-import java.util.EnumMap;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
 import java.util.Map;
+import java.util.Map.Entry;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
+import org.opendaylight.mdsal.common.api.TransactionDatastoreMismatchException;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction;
 import org.opendaylight.mdsal.dom.spi.store.DOMStoreTransaction;
 import org.opendaylight.mdsal.dom.spi.store.DOMStoreTransactionFactory;
 
 public abstract class AbstractDOMBrokerTransaction<T extends DOMStoreTransaction> implements DOMDataTreeTransaction {
 
-    private final EnumMap<LogicalDatastoreType, T> backingTxs;
-    private final Object identifier;
+    private static final VarHandle BACKING_TX;
+
+    static {
+        try {
+            BACKING_TX = MethodHandles.lookup()
+                .findVarHandle(AbstractDOMBrokerTransaction.class, "backingTx", Entry.class);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
+    private final @NonNull Object identifier;
     private final Map<LogicalDatastoreType, ? extends DOMStoreTransactionFactory> storeTxFactories;
 
+    private volatile Entry<LogicalDatastoreType, T> backingTx;
+
     /**
-     * Creates new composite Transactions.
+     * Creates new transaction.
      *
      * @param identifier Identifier of transaction.
      */
@@ -35,70 +50,68 @@ public abstract class AbstractDOMBrokerTransaction<T extends DOMStoreTransaction
             Map<LogicalDatastoreType, ? extends DOMStoreTransactionFactory> storeTxFactories) {
         this.identifier = requireNonNull(identifier, "Identifier should not be null");
         this.storeTxFactories = requireNonNull(storeTxFactories, "Store Transaction Factories should not be null");
-        this.backingTxs = new EnumMap<>(LogicalDatastoreType.class);
+        checkArgument(!storeTxFactories.isEmpty(), "Store Transaction Factories should not be empty");
     }
 
     /**
-     * Returns subtransaction associated with supplied key.
+     * Returns sub-transaction associated with supplied key.
      *
-     * @param key the data store type key
-     * @return the subtransaction
-     * @throws NullPointerException
-     *             if key is null
-     * @throws IllegalArgumentException
-     *             if no subtransaction is associated with key.
+     * @param datastoreType the data store type
+     * @return the sub-transaction
+     * @throws NullPointerException                  if datastoreType is null
+     * @throws IllegalArgumentException              if no sub-transaction is associated with datastoreType.
+     * @throws TransactionDatastoreMismatchException if datastoreType mismatches the one used at first access
      */
-    protected final T getSubtransaction(final LogicalDatastoreType key) {
-        requireNonNull(key, "key must not be null.");
+    protected final T getSubtransaction(final LogicalDatastoreType datastoreType) {
+        requireNonNull(datastoreType, "datastoreType must not be null.");
 
-        T ret = backingTxs.get(key);
-        if (ret == null) {
-            ret = createTransaction(key);
-            backingTxs.put(key, ret);
+        var entry = backingTx;
+        if (entry == null) {
+            if (!storeTxFactories.containsKey(datastoreType)) {
+                throw new IllegalArgumentException(datastoreType + " is not supported");
+            }
+            final var tx = createTransaction(datastoreType);
+            final var newEntry = Map.entry(datastoreType, tx);
+            final var witness = (Entry<LogicalDatastoreType, T>) BACKING_TX.compareAndExchange(this, null, newEntry);
+            if (witness != null) {
+                tx.close();
+                entry = witness;
+            } else {
+                entry = newEntry;
+            }
         }
-        checkArgument(ret != null, "No subtransaction associated with %s", key);
-        return ret;
-    }
 
-    protected abstract T createTransaction(LogicalDatastoreType key);
+        final var expected = entry.getKey();
+        if (expected != datastoreType) {
+            throw new TransactionDatastoreMismatchException(expected, datastoreType);
+        }
+        return entry.getValue();
+    }
 
     /**
-     * Returns immutable Iterable of all subtransactions.
-     *
+     * Returns sub-transaction if initialized.
      */
-    protected Collection<T> getSubtransactions() {
-        return backingTxs.values();
+    protected T getSubtransaction() {
+        final Entry<LogicalDatastoreType, T> entry;
+        return (entry = backingTx) == null ? null : entry.getValue();
     }
 
+    protected abstract T createTransaction(LogicalDatastoreType datastoreType);
+
     @Override
     public Object getIdentifier() {
         return identifier;
     }
 
     @SuppressWarnings("checkstyle:IllegalCatch")
-    protected void closeSubtransactions() {
-        /*
-         * We share one exception for all failures, which are added
-         * as supressedExceptions to it.
-         */
-        IllegalStateException failure = null;
-        for (T subtransaction : backingTxs.values()) {
+    protected void closeSubtransaction() {
+        if (backingTx != null) {
             try {
-                subtransaction.close();
+                backingTx.getValue().close();
             } catch (Exception e) {
-                // If we did not allocated failure we allocate it
-                if (failure == null) {
-                    failure = new IllegalStateException("Uncaught exception occured during closing transaction", e);
-                } else {
-                    // We update it with additional exceptions, which occurred during error.
-                    failure.addSuppressed(e);
-                }
+                throw new IllegalStateException("Uncaught exception occurred during closing transaction", e);
             }
         }
-        // If we have failure, we throw it at after all attempts to close.
-        if (failure != null) {
-            throw failure;
-        }
     }
 
     protected DOMStoreTransactionFactory getTxFactory(LogicalDatastoreType type) {