Fix a race PingPongTransactionChain 06/14106/2
authorRobert Varga <rovarga@cisco.com>
Mon, 12 Jan 2015 15:52:00 +0000 (16:52 +0100)
committerRobert Varga <rovarga@cisco.com>
Mon, 12 Jan 2015 16:12:45 +0000 (17:12 +0100)
As it turns out, we have made an illegal shortcut while readying a transaction. This results in the following exception,
which occurs if we happen to want to allocate a transaction while it is being submitted to backed.

Exception in thread "Thread-18" java.lang.IllegalStateException: Previous transaction DOM-OPER-9 is not ready yet
        at com.google.common.base.Preconditions.checkState(Preconditions.java:176)
        at org.opendaylight.controller.md.sal.dom.store.impl.DOMStoreTransactionChainImpl$Allocated.getSnapshot(DOMStoreTransactionChainImpl.java:68)
        at org.opendaylight.controller.md.sal.dom.store.impl.DOMStoreTransactionChainImpl.getSnapshot(DOMStoreTransactionChainImpl.java:111)
        at org.opendaylight.controller.md.sal.dom.store.impl.DOMStoreTransactionChainImpl.newReadWriteTransaction(DOMStoreTransactionChainImpl.java:131)
        at org.opendaylight.controller.md.sal.dom.broker.impl.AbstractDOMForwardedTransactionFactory.newReadWriteTransaction(AbstractDOMForwardedTransactionFactory.java:206)
        at org.opendaylight.controller.md.sal.dom.broker.impl.PingPongTransactionChain.slowAllocateTransaction(PingPongTransactionChain.java:128)
        at org.opendaylight.controller.md.sal.dom.broker.impl.PingPongTransactionChain.allocateTransaction(PingPongTransactionChain.java:145)
        at org.opendaylight.controller.md.sal.dom.broker.impl.PingPongTransactionChain.newReadWriteTransaction(PingPongTransactionChain.java:279)
        at org.opendaylight.controller.md.sal.dom.broker.impl.PingPongTransactionChain.newWriteOnlyTransaction(PingPongTransactionChain.java:310)
        at org.opendaylight.controller.md.sal.binding.impl.BindingTranslatedTransactionChain.newWriteOnlyTransaction(BindingTranslatedTransactionChain.java:77)
        at org.opendaylight.protocol.bgp.rib.impl.RIBImpl$1.run(RIBImpl.java:125)
        at java.lang.Thread.run(Thread.java:745)

Change-Id: Ie21cf5f0ebba79fa25358da2ee1e6b22cfb0f906
Signed-off-by: Robert Varga <rovarga@cisco.com>
opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongTransactionChain.java

index abd348a9c73404b79f0866160f336bdee30aab59..c3a56ed454024b3cb316c1e5bec9d7184c897edc 100644 (file)
@@ -64,7 +64,6 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
      */
     private static final AtomicReferenceFieldUpdater<PingPongTransactionChain, PingPongTransaction> READY_UPDATER =
             AtomicReferenceFieldUpdater.newUpdater(PingPongTransactionChain.class, PingPongTransaction.class, "readyTx");
-    @SuppressWarnings("unused") // Accessed via READY_UPDATER
     private volatile PingPongTransaction readyTx;
 
     /**
@@ -157,7 +156,10 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
         return oldTx;
     }
 
-    // This forces allocateTransaction() on a slow path
+    /*
+     * This forces allocateTransaction() on a slow path, which has to happen after
+     * this method has completed executing.
+     */
     @GuardedBy("this")
     private void processIfReady() {
         final PingPongTransaction tx = READY_UPDATER.getAndSet(this, null);
@@ -222,14 +224,28 @@ public final class PingPongTransactionChain implements DOMTransactionChain {
     }
 
     private void readyTransaction(final @Nonnull PingPongTransaction tx) {
+        // First mark the transaction as not locked.
         final boolean lockedMatch = LOCKED_UPDATER.compareAndSet(this, tx, null);
         Preconditions.checkState(lockedMatch, "Attempted to submit transaction %s while we have %s", tx, lockedTx);
-
         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);
+        Preconditions.checkState(success, "Transaction %s collided on ready state", tx, readyTx);
+        LOG.debug("Transaction {} readied");
+
+        /*
+         * We do not see a transaction being in-flight, so we need to take care of dispatching
+         * the transaction to the backend. We are in the ready case, we cannot short-cut
+         * the checking of readyTx, as an in-flight transaction may have completed between us
+         * setting the field above and us checking.
+         */
         if (inflightTx == null) {
             synchronized (this) {
-                processTransaction(tx);
+                processIfReady();
             }
         }
     }