Use VarHandles in PingPongTransactionChain
[mdsal.git] / dom / mdsal-dom-spi / src / main / java / org / opendaylight / mdsal / dom / spi / PingPongTransactionChain.java
index 1a6d1608ecfae37415ce5811e0f4e6d99129c0e1..8a342fbaa2b3a9c0ad5010cd924d5ac90c23de3b 100644 (file)
@@ -14,14 +14,17 @@ import static java.util.Objects.requireNonNull;
 import com.google.common.util.concurrent.FluentFuture;
 import com.google.common.util.concurrent.FutureCallback;
 import com.google.common.util.concurrent.MoreExecutors;
+import java.lang.invoke.MethodHandles;
+import java.lang.invoke.VarHandle;
 import java.util.AbstractMap.SimpleImmutableEntry;
 import java.util.Map.Entry;
 import java.util.Optional;
 import java.util.concurrent.CancellationException;
-import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 import java.util.function.Function;
-import javax.annotation.concurrent.GuardedBy;
+import org.checkerframework.checker.lock.qual.GuardedBy;
+import org.checkerframework.checker.lock.qual.Holding;
 import org.eclipse.jdt.annotation.NonNull;
+import org.eclipse.jdt.annotation.Nullable;
 import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
@@ -63,35 +66,41 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
     @GuardedBy("this")
     private Entry<PingPongTransaction, Throwable> deadTx;
 
-    //  This updater is used to manipulate the "ready" transaction. We perform only atomic get-and-set on it.
-    private static final AtomicReferenceFieldUpdater<PingPongTransactionChain, PingPongTransaction> READY_UPDATER =
-            AtomicReferenceFieldUpdater.newUpdater(PingPongTransactionChain.class, PingPongTransaction.class,
-                "readyTx");
+    //  This VarHandle is used to manipulate the "ready" transaction. We perform only atomic get-and-set on it.
+    private static final VarHandle READY_TX;
+    @SuppressWarnings("unused")
     private volatile PingPongTransaction readyTx;
 
     /*
-     * This updater is used to manipulate the "locked" transaction. A locked transaction means we know that the user
+     * This VarHandle is used to manipulate the "locked" transaction. A locked transaction means we know that the user
      * still holds a transaction and should at some point call us. We perform on compare-and-swap to ensure we properly
      * detect when a user is attempting to allocated multiple transactions concurrently.
      */
-    private static final AtomicReferenceFieldUpdater<PingPongTransactionChain, PingPongTransaction> LOCKED_UPDATER =
-            AtomicReferenceFieldUpdater.newUpdater(PingPongTransactionChain.class, PingPongTransaction.class,
-                "lockedTx");
+    private static final VarHandle LOCKED_TX;
     private volatile PingPongTransaction lockedTx;
 
     /*
      * This updater is used to manipulate the "inflight" transaction. There can be at most one of these at any given
      * time. We perform only compare-and-swap on these.
      */
-    private static final AtomicReferenceFieldUpdater<PingPongTransactionChain, PingPongTransaction> INFLIGHT_UPDATER =
-            AtomicReferenceFieldUpdater.newUpdater(PingPongTransactionChain.class, PingPongTransaction.class,
-                "inflightTx");
+    private static final VarHandle INFLIGHT_TX;
     private volatile PingPongTransaction inflightTx;
 
+    static {
+        final var lookup = MethodHandles.lookup();
+        try {
+            INFLIGHT_TX = lookup.findVarHandle(PingPongTransactionChain.class, "inflightTx", PingPongTransaction.class);
+            LOCKED_TX = lookup.findVarHandle(PingPongTransactionChain.class, "lockedTx", PingPongTransaction.class);
+            READY_TX = lookup.findVarHandle(PingPongTransactionChain.class, "readyTx", PingPongTransaction.class);
+        } catch (NoSuchFieldException | IllegalAccessException e) {
+            throw new ExceptionInInitializerError(e);
+        }
+    }
+
     public PingPongTransactionChain(final Function<DOMTransactionChainListener, DOMTransactionChain> delegateFactory,
             final DOMTransactionChainListener listener) {
         this.listener = requireNonNull(listener);
-        this.delegate = delegateFactory.apply(new DOMTransactionChainListener() {
+        delegate = delegateFactory.apply(new DOMTransactionChainListener() {
             @Override
             public void onTransactionChainFailed(final DOMTransactionChain chain,
                     final DOMDataTreeTransaction transaction, final Throwable cause) {
@@ -166,18 +175,23 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
         final DOMDataTreeReadWriteTransaction delegateTx = delegate.newReadWriteTransaction();
         final PingPongTransaction newTx = new PingPongTransaction(delegateTx);
 
-        if (!LOCKED_UPDATER.compareAndSet(this, null, newTx)) {
+        final Object witness = LOCKED_TX.compareAndExchange(this, null, newTx);
+        if (witness != null) {
             delegateTx.cancel();
             throw new IllegalStateException(
-                    String.format("New transaction %s raced with transaction %s", newTx, lockedTx));
+                    String.format("New transaction %s raced with transaction %s", newTx, witness));
         }
 
         return newTx;
     }
 
+    private @Nullable PingPongTransaction acquireReadyTx() {
+        return (PingPongTransaction) READY_TX.getAndSet(this, null);
+    }
+
     private @NonNull PingPongTransaction allocateTransaction() {
         // Step 1: acquire current state
-        final PingPongTransaction oldTx = READY_UPDATER.getAndSet(this, null);
+        final PingPongTransaction oldTx = acquireReadyTx();
 
         // Slow path: allocate a delegate transaction
         if (oldTx == null) {
@@ -185,11 +199,12 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
         }
 
         // Fast path: reuse current transaction. We will check failures and similar on commit().
-        if (!LOCKED_UPDATER.compareAndSet(this, null, oldTx)) {
+        final Object witness = LOCKED_TX.compareAndExchange(this, null, oldTx);
+        if (witness != null) {
             // Ouch. Delegate chain has not detected a duplicate transaction allocation. This is the best we can do.
             oldTx.getTransaction().cancel();
             throw new IllegalStateException(String.format("Reusable transaction %s raced with transaction %s", oldTx,
-                lockedTx));
+                witness));
         }
 
         return oldTx;
@@ -199,10 +214,10 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
      * This forces allocateTransaction() on a slow path, which has to happen after this method has completed executing.
      * Also inflightTx may be updated outside the lock, hence we need to re-check.
      */
-    @GuardedBy("this")
+    @Holding("this")
     private void processIfReady() {
         if (inflightTx == null) {
-            final PingPongTransaction tx = READY_UPDATER.getAndSet(this, null);
+            final PingPongTransaction tx = acquireReadyTx();
             if (tx != null) {
                 processTransaction(tx);
             }
@@ -214,7 +229,7 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
      *
      * @param tx Transaction which needs processing.
      */
-    @GuardedBy("this")
+    @Holding("this")
     private void processTransaction(final @NonNull PingPongTransaction tx) {
         if (failed) {
             LOG.debug("Cancelling transaction {}", tx);
@@ -223,8 +238,9 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
         }
 
         LOG.debug("Submitting transaction {}", tx);
-        if (!INFLIGHT_UPDATER.compareAndSet(this, null, tx)) {
-            LOG.warn("Submitting transaction {} while {} is still running", tx, inflightTx);
+        final Object witness = INFLIGHT_TX.compareAndExchange(this, null, tx);
+        if (witness != null) {
+            LOG.warn("Submitting transaction {} while {} is still running", tx, witness);
         }
 
         tx.getTransaction().commit().addCallback(new FutureCallback<CommitInfo>() {
@@ -262,16 +278,19 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
      * correctness.
      */
     private synchronized void processNextTransaction(final PingPongTransaction tx) {
-        final boolean success = INFLIGHT_UPDATER.compareAndSet(this, tx, null);
-        checkState(success, "Completed transaction %s while %s was submitted", tx, inflightTx);
-
-        final PingPongTransaction nextTx = READY_UPDATER.getAndSet(this, null);
-        if (nextTx != null) {
+        final Object witness = INFLIGHT_TX.compareAndExchange(this, tx, null);
+        checkState(witness == tx, "Completed transaction %s while %s was submitted", tx, witness);
+
+        final PingPongTransaction nextTx = acquireReadyTx();
+        if (nextTx == null) {
+            final PingPongTransaction local = shutdownTx;
+            if (local != null) {
+                processTransaction(local);
+                delegate.close();
+                shutdownTx = null;
+            }
+        } else {
             processTransaction(nextTx);
-        } else if (shutdownTx != null) {
-            processTransaction(shutdownTx);
-            delegate.close();
-            shutdownTx = null;
         }
     }
 
@@ -291,16 +310,16 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
 
     void readyTransaction(final @NonNull PingPongTransaction tx) {
         // First mark the transaction as not locked.
-        final boolean lockedMatch = LOCKED_UPDATER.compareAndSet(this, tx, null);
-        checkState(lockedMatch, "Attempted to submit transaction %s while we have %s", tx, lockedTx);
+        final Object lockedWitness = LOCKED_TX.compareAndExchange(this, tx, null);
+        checkState(lockedWitness == tx, "Attempted to submit transaction %s while we have %s", tx, lockedWitness);
         LOG.debug("Transaction {} unlocked", tx);
 
         /*
          * The transaction is ready. It will then be picked up by either next allocation,
          * or a background transaction completion callback.
          */
-        final boolean success = READY_UPDATER.compareAndSet(this, null, tx);
-        checkState(success, "Transaction %s collided on ready state", tx, readyTx);
+        final Object readyWitness = READY_TX.compareAndExchange(this, null, tx);
+        checkState(readyWitness == null, "Transaction %s collided on ready state with %s", tx, readyWitness);
         LOG.debug("Transaction {} readied", tx);
 
         /*
@@ -328,8 +347,8 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
     synchronized void cancelTransaction(final PingPongTransaction tx,
             final DOMDataTreeReadWriteTransaction frontendTx) {
         // Attempt to unlock the operation.
-        final boolean lockedMatch = LOCKED_UPDATER.compareAndSet(this, tx, null);
-        verify(lockedMatch, "Cancelling transaction %s collided with locked transaction %s", tx, lockedTx);
+        final Object witness = LOCKED_TX.compareAndExchange(this, tx, null);
+        verify(witness == tx, "Cancelling transaction %s collided with locked transaction %s", tx, witness);
 
         // Cancel the backend transaction, so we do not end up leaking it.
         final boolean backendCancelled = tx.getTransaction().cancel();
@@ -378,7 +397,7 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
         }
 
         // Force allocations on slow path, picking up a potentially-outstanding transaction
-        final PingPongTransaction tx = READY_UPDATER.getAndSet(this, null);
+        final PingPongTransaction tx = acquireReadyTx();
 
         if (tx != null) {
             // We have one more transaction, which needs to be processed somewhere. If we do not
@@ -400,68 +419,81 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
 
     @Override
     public DOMDataTreeReadTransaction newReadOnlyTransaction() {
+        return new PingPongReadTransaction(allocateTransaction());
+    }
+
+    @Override
+    public DOMDataTreeReadWriteTransaction newReadWriteTransaction() {
         final PingPongTransaction tx = allocateTransaction();
+        final DOMDataTreeReadWriteTransaction ret = new PingPongReadWriteTransaction(tx);
+        tx.recordFrontendTransaction(ret);
+        return ret;
+    }
 
-        return new DOMDataTreeReadTransaction() {
-            @Override
-            public FluentFuture<Optional<NormalizedNode<?, ?>>> read(
-                    final LogicalDatastoreType store, final YangInstanceIdentifier path) {
-                return tx.getTransaction().read(store, path);
-            }
+    @Override
+    public DOMDataTreeWriteTransaction newWriteOnlyTransaction() {
+        return newReadWriteTransaction();
+    }
 
-            @Override
-            public FluentFuture<Boolean> exists(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
-                return tx.getTransaction().exists(store, path);
-            }
+    private final class PingPongReadTransaction implements DOMDataTreeReadTransaction {
+        private final @NonNull PingPongTransaction tx;
 
-            @Override
-            public Object getIdentifier() {
-                return tx.getTransaction().getIdentifier();
-            }
+        PingPongReadTransaction(final PingPongTransaction tx) {
+            this.tx = requireNonNull(tx);
+        }
 
-            @Override
-            public void close() {
-                readyTransaction(tx);
-            }
-        };
+        @Override
+        public FluentFuture<Optional<NormalizedNode>> read(final LogicalDatastoreType store,
+                final YangInstanceIdentifier path) {
+            return tx.getTransaction().read(store, path);
+        }
+
+        @Override
+        public FluentFuture<Boolean> exists(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+            return tx.getTransaction().exists(store, path);
+        }
+
+        @Override
+        public Object getIdentifier() {
+            return tx.getTransaction().getIdentifier();
+        }
+
+        @Override
+        public void close() {
+            readyTransaction(tx);
+        }
     }
 
-    @Override
-    public DOMDataTreeReadWriteTransaction newReadWriteTransaction() {
-        final PingPongTransaction tx = allocateTransaction();
-        final DOMDataTreeReadWriteTransaction ret = new ForwardingDOMDataReadWriteTransaction() {
-            private boolean isOpen = true;
+    private final class PingPongReadWriteTransaction extends ForwardingDOMDataReadWriteTransaction {
+        private final @NonNull PingPongTransaction tx;
 
-            @Override
-            protected DOMDataTreeReadWriteTransaction delegate() {
-                return tx.getTransaction();
-            }
+        private boolean isOpen = true;
 
-            @Override
-            public FluentFuture<? extends CommitInfo> commit() {
-                readyTransaction(tx);
-                isOpen = false;
-                return tx.getCommitFuture().transform(ignored -> CommitInfo.empty(), MoreExecutors.directExecutor());
-            }
+        PingPongReadWriteTransaction(final PingPongTransaction tx) {
+            this.tx = requireNonNull(tx);
+        }
 
-            @Override
-            public boolean cancel() {
-                if (isOpen) {
-                    cancelTransaction(tx, this);
-                    isOpen = false;
-                    return true;
-                }
-
-                return false;
+        @Override
+        public FluentFuture<? extends CommitInfo> commit() {
+            readyTransaction(tx);
+            isOpen = false;
+            return tx.getCommitFuture().transform(ignored -> CommitInfo.empty(), MoreExecutors.directExecutor());
+        }
+
+        @Override
+        public boolean cancel() {
+            if (isOpen) {
+                cancelTransaction(tx, this);
+                isOpen = false;
+                return true;
             }
-        };
 
-        tx.recordFrontendTransaction(ret);
-        return ret;
-    }
+            return false;
+        }
 
-    @Override
-    public DOMDataTreeWriteTransaction newWriteOnlyTransaction() {
-        return newReadWriteTransaction();
+        @Override
+        protected DOMDataTreeReadWriteTransaction delegate() {
+            return tx.getTransaction();
+        }
     }
 }