From: Robert Varga Date: Mon, 12 Jan 2015 15:52:00 +0000 (+0100) Subject: Fix a race PingPongTransactionChain X-Git-Tag: release/lithium~705^2 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=commitdiff_plain;h=a35800d4293761e1f6a436ea1abae4d58927fd18 Fix a race PingPongTransactionChain 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 --- diff --git a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongTransactionChain.java b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongTransactionChain.java index abd348a9c7..c3a56ed454 100644 --- a/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongTransactionChain.java +++ b/opendaylight/md-sal/sal-dom-broker/src/main/java/org/opendaylight/controller/md/sal/dom/broker/impl/PingPongTransactionChain.java @@ -64,7 +64,6 @@ public final class PingPongTransactionChain implements DOMTransactionChain { */ private static final AtomicReferenceFieldUpdater 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(); } } }