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=025e51684c056ba57360c8b554ac5d5a3e210e42;hb=9b4c07ca27b2c3c7ca5d5e790aa1f121ce4857f3;hpb=e5d320150e9cb40c338bf27f31a636fa5207d3eb 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 025e51684c..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; @@ -28,6 +29,8 @@ import javax.annotation.Nullable; 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; @@ -68,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 @@ -123,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<>(); @@ -185,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); } @@ -228,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); @@ -290,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())); } } @@ -305,29 +388,51 @@ 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) { - ret.voteNo(((RequestFailure) t).getCause()); + ret.voteNo(((RequestFailure) t).getCause().unwrap()); } else { ret.voteNo(new IllegalStateException("Unhandled response " + t.getClass())); } // This is a terminal request, hence we do not need to record it LOG.debug("Transaction {} abort completed", this); - purge(); + enqueuePurge(); }); } - final void sendAbort(final Consumer> 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); } @@ -349,14 +454,14 @@ abstract class AbstractProxyTransaction implements Identifiable) t).getCause()); + ret.setException(((RequestFailure) t).getCause().unwrap()); } else { ret.setException(new IllegalStateException("Unhandled response " + t.getClass())); } // This is a terminal request, hence we do not need to record it LOG.debug("Transaction {} directCommit completed", this); - purge(); + enqueuePurge(); }); return ret; @@ -380,7 +485,7 @@ abstract class AbstractProxyTransaction implements Identifiable) t).getCause()); + ret.voteNo(((RequestFailure) t).getCause().unwrap()); } else { ret.voteNo(new IllegalStateException("Unhandled response " + t.getClass())); } @@ -411,7 +516,7 @@ abstract class AbstractProxyTransaction implements Identifiable) t).getCause()); + ret.voteNo(((RequestFailure) t).getCause().unwrap()); } else { ret.voteNo(new IllegalStateException("Unhandled response " + t.getClass())); } @@ -444,24 +549,52 @@ abstract class AbstractProxyTransaction implements Identifiable) t).getCause()); + ret.voteNo(((RequestFailure) t).getCause().unwrap()); } else { ret.voteNo(new IllegalStateException("Unhandled response " + t.getClass())); } LOG.debug("Transaction {} doCommit completed", this); - purge(); + + // Needed for ProxyHistory$Local data tree rebase points. + parent.completeTransaction(this); + + enqueuePurge(); }); } - void purge() { + private void enqueuePurge() { + enqueuePurge(null); + } + + final void enqueuePurge(final Consumer> 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 @@ -482,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.handleForwardedRemoteRequest((TransactionRequest) obj, null); - } 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(); @@ -508,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(); } } @@ -519,7 +669,6 @@ abstract class AbstractProxyTransaction implements Identifiable + * Note: this method is invoked by the predecessor on the successor. + * + * @param request Request which needs to be forwarded + * @param callback Callback to be invoked once the request completes + * @param enqueuedTicks ticker-based time stamp when the request was enqueued + */ + private void replayRequest(final TransactionRequest request, final Consumer> callback, + final long enqueuedTicks) { + if (request instanceof AbstractLocalTransactionRequest) { + handleReplayedLocalRequest((AbstractLocalTransactionRequest) request, callback, enqueuedTicks); + } else { + handleReplayedRemoteRequest(request, callback, enqueuedTicks); + } + } + // Called with the connection locked final void finishReconnect() { final SuccessorState local = getSuccessorState(); @@ -543,9 +712,12 @@ abstract class AbstractProxyTransaction implements Identifiable request, final Consumer> callback) { - final AbstractProxyTransaction successor = getSuccessorState().getSuccessor(); + final void forwardRequest(final TransactionRequest request, final Consumer> callback) { + 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) { @@ -569,26 +741,12 @@ abstract class AbstractProxyTransaction implements Identifiable commitRequest(boolean coordinated); + abstract TransactionRequest abortRequest(); - /** - * Invoked from {@link RemoteProxyTransaction} when it replays its successful requests to its successor. There is - * no equivalent of this call from {@link LocalProxyTransaction} because it does not send a request until all - * operations are packaged in the message. - * - *

- * Note: this method is invoked by the predecessor on the successor. - * - * @param request Request which needs to be forwarded - * @param callback Callback to be invoked once the request completes - */ - abstract void handleForwardedRemoteRequest(TransactionRequest request, - @Nullable Consumer> callback); + abstract TransactionRequest commitRequest(boolean coordinated); /** * Replay a request originating in this proxy to a successor remote proxy. @@ -602,6 +760,32 @@ abstract class AbstractProxyTransaction implements Identifiable request, Consumer> callback); + /** + * Invoked from {@link LocalProxyTransaction} when it replays its successful requests to its successor. + * + *

+ * Note: this method is invoked by the predecessor on the successor. + * + * @param request Request which needs to be forwarded + * @param callback Callback to be invoked once the request completes + * @param enqueuedTicks Time stamp to use for enqueue time + */ + abstract void handleReplayedLocalRequest(AbstractLocalTransactionRequest request, + @Nullable Consumer> callback, long enqueuedTicks); + + /** + * Invoked from {@link RemoteProxyTransaction} when it replays its successful requests to its successor. + * + *

+ * Note: this method is invoked by the predecessor on the successor. + * + * @param request Request which needs to be forwarded + * @param callback Callback to be invoked once the request completes + * @param enqueuedTicks Time stamp to use for enqueue time + */ + abstract void handleReplayedRemoteRequest(TransactionRequest request, + @Nullable Consumer> callback, long enqueuedTicks); + @Override public final String toString() { return MoreObjects.toStringHelper(this).add("identifier", getIdentifier()).add("state", state).toString();