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=ac91f2d0413ad8aca71391ba86921c92dc1b008b;hp=83ba07b69ac8f6e3ed253d18e420315065069e4d;hb=9b4c07ca27b2c3c7ca5d5e790aa1f121ce4857f3;hpb=3ee40198347cfb53bd0ce12ffd625cff8ed2383b 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 83ba07b69a..ac91f2d041 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 @@ -13,6 +13,7 @@ import com.google.common.base.Optional; import com.google.common.base.Preconditions; import com.google.common.base.Throwables; import com.google.common.base.Verify; +import com.google.common.collect.Iterables; import com.google.common.util.concurrent.CheckedFuture; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; @@ -29,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.IncrementTransactionSequenceRequest; import org.opendaylight.controller.cluster.access.commands.TransactionAbortRequest; import org.opendaylight.controller.cluster.access.commands.TransactionAbortSuccess; import org.opendaylight.controller.cluster.access.commands.TransactionCanCommitSuccess; @@ -69,18 +71,30 @@ 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 @@ -124,7 +151,8 @@ 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<>(); @@ -186,11 +255,18 @@ abstract class AbstractProxyTransaction implements Identifiable { + command.run(); + return behavior; + }); + } + final ActorRef localActor() { return parent.localActor(); } - private void incrementSequence(final long delta) { + final void incrementSequence(final long delta) { sequence += delta; LOG.debug("Transaction {} incremented sequence to {}", this, sequence); } @@ -229,6 +305,12 @@ abstract class AbstractProxyTransaction implements Identifiable request, final Consumer> callback, + final long enqueuedTicks) { + LOG.debug("Transaction proxy {} enqueing request {} callback {}", this, request, callback); + parent.enqueueRequest(request, callback, enqueuedTicks); + } + final void sendRequest(final TransactionRequest request, final Consumer> callback) { LOG.debug("Transaction proxy {} sending request {} callback {}", this, request, callback); parent.sendRequest(request, callback); @@ -291,12 +373,12 @@ abstract class AbstractProxyTransaction implements Identifiable response) { final Object last = successfulRequests.peekLast(); if (last instanceof IncrementSequence) { ((IncrementSequence) last).incrementDelta(); } else { - successfulRequests.addLast(new IncrementSequence()); + successfulRequests.addLast(new IncrementSequence(response.getSequence())); } } @@ -306,14 +388,18 @@ abstract class AbstractProxyTransaction implements Identifiable { + LOG.debug("Transaction {} abort completed with {}", getIdentifier(), resp); + enqueuePurge(); + }); } final void abort(final VotingFuture ret) { checkSealed(); - sendAbort(t -> { + sendDoAbort(t -> { if (t instanceof TransactionAbortSuccess) { ret.voteYes(); } else if (t instanceof RequestFailure) { @@ -324,11 +410,29 @@ abstract class AbstractProxyTransaction implements Identifiable> callback) { + final void enqueueAbort(final Consumer> callback, final long enqueuedTicks) { + checkNotSealed(); + parent.abortTransaction(this); + + enqueueRequest(abortRequest(), resp -> { + LOG.debug("Transaction {} abort completed with {}", getIdentifier(), resp); + // Purge will be sent by the predecessor's callback + if (callback != null) { + callback.accept(resp); + } + }, enqueuedTicks); + } + + final void enqueueDoAbort(final Consumer> callback, final long enqueuedTicks) { + enqueueRequest(new TransactionAbortRequest(getIdentifier(), nextSequence(), localActor()), callback, + enqueuedTicks); + } + + final void sendDoAbort(final Consumer> callback) { sendRequest(new TransactionAbortRequest(getIdentifier(), nextSequence(), localActor()), callback); } @@ -357,7 +461,7 @@ abstract class AbstractProxyTransaction implements Identifiable> callback) { + // Purge request are dispatched internally, hence should not wait + enqueuePurge(callback, parent.currentTime()); + } + + final void enqueuePurge(final Consumer> callback, final long enqueuedTicks) { + 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(); - final TransactionRequest req = new TransactionPurgeRequest(getIdentifier(), nextSequence(), localActor()); - sendRequest(req, t -> { - LOG.debug("Transaction {} purge completed", this); - parent.completeTransaction(this); - }); + enqueueRequest(new TransactionPurgeRequest(getIdentifier(), nextSequence(), localActor()), resp -> { + LOG.debug("{}: purge completed", this); + parent.purgeTransaction(this); + + if (callback != null) { + callback.accept(resp); + } + }, enqueuedTicks); } // Called with the connection unlocked @@ -483,23 +615,40 @@ 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()); + LOG.debug("{} created successor transaction proxy {}", this, successor); local.setSuccessor(successor); // Replay successful requests first - for (Object obj : successfulRequests) { - if (obj instanceof TransactionRequest) { - LOG.debug("Forwarding successful request {} to successor {}", obj, successor); - successor.replay((TransactionRequest) obj, response -> { }); - } else { - Verify.verify(obj instanceof IncrementSequence); - successor.incrementSequence(((IncrementSequence) obj).getDelta()); + if (!successfulRequests.isEmpty()) { + // We need to find a good timestamp to use for successful requests, as we do not want to time them out + // nor create timing inconsistencies in the queue -- requests are expected to be ordered by their enqueue + // time. We will pick the time of the first entry available. If there is none, we will just use current + // time, as all other requests will get enqueued afterwards. + final ConnectionEntry firstInQueue = Iterables.getFirst(enqueuedEntries, null); + final long now = firstInQueue != null ? firstInQueue.getEnqueuedTicks() : parent.currentTime(); + + for (Object obj : successfulRequests) { + if (obj instanceof TransactionRequest) { + LOG.debug("Forwarding successful request {} to successor {}", obj, successor); + successor.replayRequest((TransactionRequest) obj, resp -> { }, now); + } else { + Verify.verify(obj instanceof IncrementSequence); + final IncrementSequence increment = (IncrementSequence) obj; + successor.replayRequest(new IncrementTransactionSequenceRequest(getIdentifier(), + increment.getSequence(), localActor(), isSnapshotOnly(), increment.getDelta()), resp -> { }, + now); + LOG.debug("Incrementing sequence {} to successor {}", obj, successor); + } } + LOG.debug("{} replayed {} successful requests", getIdentifier(), successfulRequests.size()); + successfulRequests.clear(); } - LOG.debug("{} replayed {} successful requests", getIdentifier(), successfulRequests.size()); - successfulRequests.clear(); // Now replay whatever is in the connection final Iterator it = enqueuedEntries.iterator(); @@ -509,8 +658,8 @@ abstract class AbstractProxyTransaction implements Identifiable) req, e.getCallback()); + LOG.debug("Replaying queued request {} to successor {}", req, successor); + successor.replayRequest((TransactionRequest) req, e.getCallback(), e.getEnqueuedTicks()); it.remove(); } } @@ -520,7 +669,6 @@ abstract class AbstractProxyTransaction implements Identifiable request, Consumer> callback) { + private void replayRequest(final TransactionRequest request, final Consumer> callback, + final long enqueuedTicks) { if (request instanceof AbstractLocalTransactionRequest) { - handleForwardedLocalRequest((AbstractLocalTransactionRequest) request, callback); + handleReplayedLocalRequest((AbstractLocalTransactionRequest) request, callback, enqueuedTicks); } else { - handleForwardedRemoteRequest(request, callback); + handleReplayedRemoteRequest(request, callback, enqueuedTicks); } } @@ -563,8 +713,11 @@ abstract class AbstractProxyTransaction implements Identifiable request, final Consumer> callback) { - final AbstractProxyTransaction successor = getSuccessorState().getSuccessor(); + forwardToSuccessor(getSuccessorState().getSuccessor(), request, callback); + } + final void forwardToSuccessor(final AbstractProxyTransaction successor, final TransactionRequest request, + final Consumer> callback) { if (successor instanceof LocalProxyTransaction) { forwardToLocal((LocalProxyTransaction)successor, request, callback); } else if (successor instanceof RemoteProxyTransaction) { @@ -588,11 +741,11 @@ abstract class AbstractProxyTransaction implements Identifiable abortRequest(); + abstract TransactionRequest commitRequest(boolean coordinated); /** @@ -615,9 +768,10 @@ abstract class AbstractProxyTransaction implements Identifiable request, - @Nullable Consumer> callback); + abstract void handleReplayedLocalRequest(AbstractLocalTransactionRequest request, + @Nullable Consumer> callback, long enqueuedTicks); /** * Invoked from {@link RemoteProxyTransaction} when it replays its successful requests to its successor. @@ -627,9 +781,10 @@ abstract class AbstractProxyTransaction implements Identifiable request, - @Nullable Consumer> callback); + abstract void handleReplayedRemoteRequest(TransactionRequest request, + @Nullable Consumer> callback, long enqueuedTicks); @Override public final String toString() {