X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?p=controller.git;a=blobdiff_plain;f=opendaylight%2Fmd-sal%2Fsal-distributed-datastore%2Fsrc%2Fmain%2Fjava%2Forg%2Fopendaylight%2Fcontroller%2Fcluster%2Fdatabroker%2Factors%2Fdds%2FAbstractProxyTransaction.java;h=84546632f099bb3302938f6d0e8990c3f93e5e3b;hp=bf56376fcee3d538d483a063d652ae6d3d5a5654;hb=da42d2ffc8904b8dd24596cf6d918a0d30c8c521;hpb=20ece8c549211d1c453f1763132bb0a0ca7be0e0 diff --git a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/databroker/actors/dds/AbstractProxyTransaction.java b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/databroker/actors/dds/AbstractProxyTransaction.java index bf56376fce..84546632f0 100644 --- a/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/databroker/actors/dds/AbstractProxyTransaction.java +++ b/opendaylight/md-sal/sal-distributed-datastore/src/main/java/org/opendaylight/controller/cluster/databroker/actors/dds/AbstractProxyTransaction.java @@ -30,6 +30,7 @@ import javax.annotation.concurrent.GuardedBy; import javax.annotation.concurrent.NotThreadSafe; import org.opendaylight.controller.cluster.access.client.ConnectionEntry; import org.opendaylight.controller.cluster.access.commands.AbstractLocalTransactionRequest; +import org.opendaylight.controller.cluster.access.commands.ClosedTransactionException; import org.opendaylight.controller.cluster.access.commands.IncrementTransactionSequenceRequest; import org.opendaylight.controller.cluster.access.commands.TransactionAbortRequest; import org.opendaylight.controller.cluster.access.commands.TransactionAbortSuccess; @@ -91,7 +92,10 @@ abstract class AbstractProxyTransaction implements Identifiable + * When a user operation encounters this state, it synchronizes on the it and wait until reconnection completes, + * at which point the request is routed to the successor transaction. This is a relatively heavy-weight solution + * to the problem of state transfer, but the user will observe it only if the race condition is hit. + */ + private static class SuccessorState extends State { private final CountDownLatch latch = new CountDownLatch(1); private AbstractProxyTransaction successor; private State prevState; + // SUCCESSOR + DONE + private boolean done; + SuccessorState() { - super("successor"); + super("SUCCESSOR"); } // Synchronize with succession process and return the successor @@ -131,12 +148,15 @@ abstract class AbstractProxyTransaction implements Identifiable STATE_UPDATER = AtomicReferenceFieldUpdater.newUpdater(AbstractProxyTransaction.class, State.class, "state"); - private static final State OPEN = new State("open"); - private static final State SEALED = new State("sealed"); - private static final State FLUSHED = new State("flushed"); + + /** + * Transaction has been open and is being actively worked on. + */ + private static final State OPEN = new State("OPEN"); + + /** + * Transaction has been sealed by the user, but it has not completed flushing to the backed, yet. This is + * a transition state, as we are waiting for the user to initiate commit procedures. + * + *

+ * Since the reconnect mechanics relies on state replay for transactions, this state needs to be flushed into the + * queue to re-create state in successor transaction (which may be based on different messages as locality may have + * changed). Hence the transition to {@link #FLUSHED} state needs to be handled in a thread-safe manner. + */ + private static final State SEALED = new State("SEALED"); + + /** + * Transaction state has been flushed into the queue, i.e. it is visible by the successor and potentially + * the backend. At this point the transaction does not hold any state besides successful requests, all other state + * is held either in the connection's queue or the successor object. + * + *

+ * Transition to this state indicates we have all input from the user we need to initiate the correct commit + * protocol. + */ + private static final State FLUSHED = new State("FLUSHED"); + + /** + * Transaction state has been completely resolved, we have received confirmation of the transaction fate from + * the backend. The only remaining task left to do is finishing up the state cleanup, which is done via purge + * request. We need to hang on to the transaction until that is done, as we have to make sure backend completes + * purging its state -- otherwise we could have a leak on the backend. + */ + private static final State DONE = new State("DONE"); // Touched from client actor thread only private final Deque successfulRequests = new ArrayDeque<>(); @@ -190,11 +251,18 @@ abstract class AbstractProxyTransaction implements Identifiable) t).getCause().unwrap()); + final Throwable cause = ((RequestFailure) t).getCause().unwrap(); + if (cause instanceof ClosedTransactionException) { + // This is okay, as it indicates the transaction has been completed. It can happen + // when we lose connectivity with the backend after it has received the request. + ret.set(Boolean.TRUE); + } else { + ret.setException(cause); + } } else { ret.setException(new IllegalStateException("Unhandled response " + t.getClass())); } @@ -497,6 +572,10 @@ abstract class AbstractProxyTransaction implements Identifiable> callback, final long enqueuedTicks) { - enqueueRequest(purgeRequest(), resp -> { - LOG.debug("Transaction {} purge completed", this); - parent.completeTransaction(this); + LOG.debug("{}: initiating purge", this); + + final State prev = state; + if (prev instanceof SuccessorState) { + ((SuccessorState) prev).setDone(); + } else { + final boolean success = STATE_UPDATER.compareAndSet(this, prev, DONE); + if (!success) { + LOG.warn("{}: moved from state {} while we were purging it", this, prev); + } + } + + successfulRequests.clear(); + + enqueueRequest(new TransactionPurgeRequest(getIdentifier(), nextSequence(), localActor()), resp -> { + LOG.debug("{}: purge completed", this); + parent.purgeTransaction(this); + if (callback != null) { callback.accept(resp); } }, enqueuedTicks); } - private TransactionPurgeRequest purgeRequest() { - successfulRequests.clear(); - return new TransactionPurgeRequest(getIdentifier(), nextSequence(), localActor()); - } - // Called with the connection unlocked final synchronized void startReconnect() { // At this point canCommit/directCommit are blocked, we assert a new successor state, retrieving the previous @@ -543,9 +632,13 @@ abstract class AbstractProxyTransaction implements Identifiable enqueuedEntries) { + final void replayMessages(final ProxyHistory successorHistory, final Iterable enqueuedEntries) { final SuccessorState local = getSuccessorState(); + final State prevState = local.getPrevState(); + + final AbstractProxyTransaction successor = successorHistory.createTransactionProxy(getIdentifier(), + isSnapshotOnly(), local.isDone()); + LOG.debug("{} created successor {}", this, successor); local.setSuccessor(successor); // Replay successful requests first @@ -560,11 +653,11 @@ abstract class AbstractProxyTransaction implements Identifiable) obj, resp -> { }, now); + successor.doReplayRequest((TransactionRequest) obj, resp -> { }, now); } else { Verify.verify(obj instanceof IncrementSequence); final IncrementSequence increment = (IncrementSequence) obj; - successor.replayRequest(new IncrementTransactionSequenceRequest(getIdentifier(), + successor.doReplayRequest(new IncrementTransactionSequenceRequest(getIdentifier(), increment.getSequence(), localActor(), isSnapshotOnly(), increment.getDelta()), resp -> { }, now); LOG.debug("Incrementing sequence {} to successor {}", obj, successor); @@ -583,7 +676,7 @@ abstract class AbstractProxyTransaction implements Identifiable) req, e.getCallback(), e.getEnqueuedTicks()); + successor.doReplayRequest((TransactionRequest) req, e.getCallback(), e.getEnqueuedTicks()); it.remove(); } } @@ -593,7 +686,6 @@ abstract class AbstractProxyTransaction implements Identifiable request, final Consumer> callback, + private void doReplayRequest(final TransactionRequest request, final Consumer> callback, final long enqueuedTicks) { if (request instanceof AbstractLocalTransactionRequest) { handleReplayedLocalRequest((AbstractLocalTransactionRequest) request, callback, enqueuedTicks); @@ -652,6 +744,11 @@ abstract class AbstractProxyTransaction implements Identifiable request, final Consumer> callback, + final long enqueuedTicks) { + getSuccessorState().getSuccessor().doReplayRequest(request, callback, enqueuedTicks); + } + abstract boolean isSnapshotOnly(); abstract void doDelete(YangInstanceIdentifier path);