Search in sources :

Example 1 with OnComplete

use of akka.dispatch.OnComplete in project controller by opendaylight.

the class ShardTest method testConcurrentThreePhaseCommits.

@Test
@SuppressWarnings("checkstyle:IllegalCatch")
public void testConcurrentThreePhaseCommits() throws Exception {
    final AtomicReference<Throwable> caughtEx = new AtomicReference<>();
    final CountDownLatch commitLatch = new CountDownLatch(2);
    final long timeoutSec = 5;
    final FiniteDuration duration = FiniteDuration.create(timeoutSec, TimeUnit.SECONDS);
    final Timeout timeout = new Timeout(duration);
    final TestActorRef<Shard> shard = actorFactory.createTestActor(newShardProps().withDispatcher(Dispatchers.DefaultDispatcherId()), "testConcurrentThreePhaseCommits");
    class OnFutureComplete extends OnComplete<Object> {

        private final Class<?> expRespType;

        OnFutureComplete(final Class<?> expRespType) {
            this.expRespType = expRespType;
        }

        @Override
        public void onComplete(final Throwable error, final Object resp) {
            if (error != null) {
                caughtEx.set(new AssertionError(getClass().getSimpleName() + " failure", error));
            } else {
                try {
                    assertEquals("Commit response type", expRespType, resp.getClass());
                    onSuccess(resp);
                } catch (final Exception e) {
                    caughtEx.set(e);
                }
            }
        }

        void onSuccess(final Object resp) throws Exception {
        }
    }
    class OnCommitFutureComplete extends OnFutureComplete {

        OnCommitFutureComplete() {
            super(CommitTransactionReply.class);
        }

        @Override
        public void onComplete(final Throwable error, final Object resp) {
            super.onComplete(error, resp);
            commitLatch.countDown();
        }
    }
    class OnCanCommitFutureComplete extends OnFutureComplete {

        private final TransactionIdentifier transactionID;

        OnCanCommitFutureComplete(final TransactionIdentifier transactionID) {
            super(CanCommitTransactionReply.class);
            this.transactionID = transactionID;
        }

        @Override
        void onSuccess(final Object resp) throws Exception {
            final CanCommitTransactionReply canCommitReply = CanCommitTransactionReply.fromSerializable(resp);
            assertEquals("Can commit", true, canCommitReply.getCanCommit());
            final Future<Object> commitFuture = Patterns.ask(shard, new CommitTransaction(transactionID, CURRENT_VERSION).toSerializable(), timeout);
            commitFuture.onComplete(new OnCommitFutureComplete(), getSystem().dispatcher());
        }
    }
    new ShardTestKit(getSystem()) {

        {
            waitUntilLeader(shard);
            final TransactionIdentifier transactionID1 = nextTransactionId();
            final TransactionIdentifier transactionID2 = nextTransactionId();
            final TransactionIdentifier transactionID3 = nextTransactionId();
            final Map<TransactionIdentifier, CapturingShardDataTreeCohort> cohortMap = setupCohortDecorator(shard.underlyingActor(), transactionID1, transactionID2, transactionID3);
            final CapturingShardDataTreeCohort cohort1 = cohortMap.get(transactionID1);
            final CapturingShardDataTreeCohort cohort2 = cohortMap.get(transactionID2);
            final CapturingShardDataTreeCohort cohort3 = cohortMap.get(transactionID3);
            shard.tell(prepareBatchedModifications(transactionID1, TestModel.TEST_PATH, ImmutableNodes.containerNode(TestModel.TEST_QNAME), false), getRef());
            final ReadyTransactionReply readyReply = ReadyTransactionReply.fromSerializable(expectMsgClass(duration, ReadyTransactionReply.class));
            assertEquals("Cohort path", shard.path().toString(), readyReply.getCohortPath());
            // Send the CanCommitTransaction message for the first Tx.
            shard.tell(new CanCommitTransaction(transactionID1, CURRENT_VERSION).toSerializable(), getRef());
            final CanCommitTransactionReply canCommitReply = CanCommitTransactionReply.fromSerializable(expectMsgClass(duration, CanCommitTransactionReply.class));
            assertEquals("Can commit", true, canCommitReply.getCanCommit());
            // Ready 2 more Tx's.
            shard.tell(prepareBatchedModifications(transactionID2, TestModel.OUTER_LIST_PATH, ImmutableNodes.mapNodeBuilder(TestModel.OUTER_LIST_QNAME).build(), false), getRef());
            expectMsgClass(duration, ReadyTransactionReply.class);
            shard.tell(prepareBatchedModifications(transactionID3, YangInstanceIdentifier.builder(TestModel.OUTER_LIST_PATH).nodeWithKey(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1).build(), ImmutableNodes.mapEntry(TestModel.OUTER_LIST_QNAME, TestModel.ID_QNAME, 1), false), getRef());
            expectMsgClass(duration, ReadyTransactionReply.class);
            // Send the CanCommitTransaction message for the next 2 Tx's.
            // These should get queued and
            // processed after the first Tx completes.
            final Future<Object> canCommitFuture1 = Patterns.ask(shard, new CanCommitTransaction(transactionID2, CURRENT_VERSION).toSerializable(), timeout);
            final Future<Object> canCommitFuture2 = Patterns.ask(shard, new CanCommitTransaction(transactionID3, CURRENT_VERSION).toSerializable(), timeout);
            // Send the CommitTransaction message for the first Tx. After it
            // completes, it should
            // trigger the 2nd Tx to proceed which should in turn then
            // trigger the 3rd.
            shard.tell(new CommitTransaction(transactionID1, CURRENT_VERSION).toSerializable(), getRef());
            expectMsgClass(duration, CommitTransactionReply.class);
            // Wait for the next 2 Tx's to complete.
            canCommitFuture1.onComplete(new OnCanCommitFutureComplete(transactionID2), getSystem().dispatcher());
            canCommitFuture2.onComplete(new OnCanCommitFutureComplete(transactionID3), getSystem().dispatcher());
            final boolean done = commitLatch.await(timeoutSec, TimeUnit.SECONDS);
            final Throwable t = caughtEx.get();
            if (t != null) {
                Throwables.propagateIfPossible(t, Exception.class);
                throw new RuntimeException(t);
            }
            assertEquals("Commits complete", true, done);
            // final InOrder inOrder = inOrder(cohort1.getCanCommit(), cohort1.getPreCommit(), cohort1.getCommit(),
            // cohort2.getCanCommit(), cohort2.getPreCommit(), cohort2.getCommit(), cohort3.getCanCommit(),
            // cohort3.getPreCommit(), cohort3.getCommit());
            // inOrder.verify(cohort1.getCanCommit()).onSuccess(any(Void.class));
            // inOrder.verify(cohort1.getPreCommit()).onSuccess(any(DataTreeCandidate.class));
            // inOrder.verify(cohort2.getCanCommit()).onSuccess(any(Void.class));
            // inOrder.verify(cohort2.getPreCommit()).onSuccess(any(DataTreeCandidate.class));
            // inOrder.verify(cohort3.getCanCommit()).onSuccess(any(Void.class));
            // inOrder.verify(cohort3.getPreCommit()).onSuccess(any(DataTreeCandidate.class));
            // inOrder.verify(cohort1.getCommit()).onSuccess(any(UnsignedLong.class));
            // inOrder.verify(cohort2.getCommit()).onSuccess(any(UnsignedLong.class));
            // inOrder.verify(cohort3.getCommit()).onSuccess(any(UnsignedLong.class));
            // Verify data in the data store.
            verifyOuterListEntry(shard, 1);
            verifyLastApplied(shard, 5);
        }
    };
}
Also used : ReadyTransactionReply(org.opendaylight.controller.cluster.datastore.messages.ReadyTransactionReply) CommitTransactionReply(org.opendaylight.controller.cluster.datastore.messages.CommitTransactionReply) CanCommitTransactionReply(org.opendaylight.controller.cluster.datastore.messages.CanCommitTransactionReply) CanCommitTransaction(org.opendaylight.controller.cluster.datastore.messages.CanCommitTransaction) CommitTransaction(org.opendaylight.controller.cluster.datastore.messages.CommitTransaction) TransactionIdentifier(org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier) Timeout(akka.util.Timeout) ElectionTimeout(org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout) FiniteDuration(scala.concurrent.duration.FiniteDuration) OnComplete(akka.dispatch.OnComplete) AtomicReference(java.util.concurrent.atomic.AtomicReference) CountDownLatch(java.util.concurrent.CountDownLatch) IOException(java.io.IOException) ReadFailedException(org.opendaylight.controller.md.sal.common.api.data.ReadFailedException) NoShardLeaderException(org.opendaylight.controller.cluster.datastore.exceptions.NoShardLeaderException) DataValidationFailedException(org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException) CanCommitTransactionReply(org.opendaylight.controller.cluster.datastore.messages.CanCommitTransactionReply) CanCommitTransaction(org.opendaylight.controller.cluster.datastore.messages.CanCommitTransaction) Test(org.junit.Test)

Example 2 with OnComplete

use of akka.dispatch.OnComplete in project controller by opendaylight.

the class ThreePhaseCommitCohortProxy method finishCanCommit.

private void finishCanCommit(final SettableFuture<Boolean> returnFuture) {
    LOG.debug("Tx {} finishCanCommit", transactionId);
    // For empty transactions return immediately
    if (cohorts.size() == 0) {
        LOG.debug("Tx {}: canCommit returning result true", transactionId);
        returnFuture.set(Boolean.TRUE);
        return;
    }
    commitOperationCallback = new TransactionRateLimitingCallback(actorContext);
    commitOperationCallback.run();
    final Iterator<CohortInfo> iterator = cohorts.iterator();
    final OnComplete<Object> onComplete = new OnComplete<Object>() {

        @Override
        public void onComplete(final Throwable failure, final Object response) {
            if (failure != null) {
                LOG.debug("Tx {}: a canCommit cohort Future failed", transactionId, failure);
                returnFuture.setException(failure);
                commitOperationCallback.failure();
                return;
            }
            // Only the first call to pause takes effect - subsequent calls before resume are no-ops. So
            // this means we'll only time the first transaction canCommit which should be fine.
            commitOperationCallback.pause();
            boolean result = true;
            if (CanCommitTransactionReply.isSerializedType(response)) {
                CanCommitTransactionReply reply = CanCommitTransactionReply.fromSerializable(response);
                LOG.debug("Tx {}: received {}", transactionId, response);
                if (!reply.getCanCommit()) {
                    result = false;
                }
            } else {
                LOG.error("Unexpected response type {}", response.getClass());
                returnFuture.setException(new IllegalArgumentException(String.format("Unexpected response type %s", response.getClass())));
                return;
            }
            if (iterator.hasNext() && result) {
                sendCanCommitTransaction(iterator.next(), this);
            } else {
                LOG.debug("Tx {}: canCommit returning result: {}", transactionId, result);
                returnFuture.set(Boolean.valueOf(result));
            }
        }
    };
    sendCanCommitTransaction(iterator.next(), onComplete);
}
Also used : OnComplete(akka.dispatch.OnComplete) CanCommitTransactionReply(org.opendaylight.controller.cluster.datastore.messages.CanCommitTransactionReply)

Example 3 with OnComplete

use of akka.dispatch.OnComplete in project controller by opendaylight.

the class TransactionChainProxy method findPrimaryShard.

/**
 * This method is overridden to ensure the previous Tx's ready operations complete
 * before we initiate the next Tx in the chain to avoid creation failures if the
 * previous Tx's ready operations haven't completed yet.
 */
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
protected Future<PrimaryShardInfo> findPrimaryShard(final String shardName, final TransactionIdentifier txId) {
    // Read current state atomically
    final State localState = currentState;
    // There are no outstanding futures, shortcut
    Future<?> previous = localState.previousFuture();
    if (previous == null) {
        return combineFutureWithPossiblePriorReadOnlyTxFutures(parent.findPrimaryShard(shardName, txId), txId);
    }
    final String previousTransactionId;
    if (localState instanceof Pending) {
        previousTransactionId = ((Pending) localState).getIdentifier().toString();
        LOG.debug("Tx: {} - waiting for ready futures with pending Tx {}", txId, previousTransactionId);
    } else {
        previousTransactionId = "";
        LOG.debug("Waiting for ready futures on chain {}", getHistoryId());
    }
    previous = combineFutureWithPossiblePriorReadOnlyTxFutures(previous, txId);
    // Add a callback for completion of the combined Futures.
    final Promise<PrimaryShardInfo> returnPromise = Futures.promise();
    final OnComplete onComplete = new OnComplete() {

        @Override
        public void onComplete(final Throwable failure, final Object notUsed) {
            if (failure != null) {
                // A Ready Future failed so fail the returned Promise.
                LOG.error("Tx: {} - ready future failed for previous Tx {}", txId, previousTransactionId);
                returnPromise.failure(failure);
            } else {
                LOG.debug("Tx: {} - previous Tx {} readied - proceeding to FindPrimaryShard", txId, previousTransactionId);
                // Send the FindPrimaryShard message and use the resulting Future to complete the
                // returned Promise.
                returnPromise.completeWith(parent.findPrimaryShard(shardName, txId));
            }
        }
    };
    previous.onComplete(onComplete, getActorContext().getClientDispatcher());
    return returnPromise.future();
}
Also used : PrimaryShardInfo(org.opendaylight.controller.cluster.datastore.messages.PrimaryShardInfo) OnComplete(akka.dispatch.OnComplete)

Example 4 with OnComplete

use of akka.dispatch.OnComplete in project controller by opendaylight.

the class RemoteTransactionContext method executeRead.

@Override
public <T> void executeRead(final AbstractRead<T> readCmd, final SettableFuture<T> returnFuture) {
    LOG.debug("Tx {} executeRead {} called path = {}", getIdentifier(), readCmd.getClass().getSimpleName(), readCmd.getPath());
    final Throwable failure = failedModification;
    if (failure != null) {
        // If we know there was a previous modification failure, we must not send a read request, as it risks
        // returning incorrect data. We check this before acquiring an operation simply because we want the app
        // to complete this transaction as soon as possible.
        returnFuture.setException(new ReadFailedException("Previous modification failed, cannot " + readCmd.getClass().getSimpleName() + " for path " + readCmd.getPath(), failure));
        return;
    }
    // Send any batched modifications. This is necessary to honor the read uncommitted semantics of the
    // public API contract.
    final boolean havePermit = acquireOperation();
    sendBatchedModifications();
    OnComplete<Object> onComplete = new OnComplete<Object>() {

        @Override
        public void onComplete(Throwable failure, Object response) {
            // We have previously acquired an operation, now release it, no matter what happened
            if (havePermit) {
                limiter.release();
            }
            if (failure != null) {
                LOG.debug("Tx {} {} operation failed: {}", getIdentifier(), readCmd.getClass().getSimpleName(), failure);
                returnFuture.setException(new ReadFailedException("Error checking " + readCmd.getClass().getSimpleName() + " for path " + readCmd.getPath(), failure));
            } else {
                LOG.debug("Tx {} {} operation succeeded", getIdentifier(), readCmd.getClass().getSimpleName());
                readCmd.processResponse(response, returnFuture);
            }
        }
    };
    final Future<Object> future = actorContext.executeOperationAsync(getActor(), readCmd.asVersion(getTransactionVersion()).toSerializable(), actorContext.getOperationTimeout());
    future.onComplete(onComplete, actorContext.getClientDispatcher());
}
Also used : ReadFailedException(org.opendaylight.controller.md.sal.common.api.data.ReadFailedException) OnComplete(akka.dispatch.OnComplete)

Aggregations

OnComplete (akka.dispatch.OnComplete)4 CanCommitTransactionReply (org.opendaylight.controller.cluster.datastore.messages.CanCommitTransactionReply)2 ReadFailedException (org.opendaylight.controller.md.sal.common.api.data.ReadFailedException)2 Timeout (akka.util.Timeout)1 IOException (java.io.IOException)1 CountDownLatch (java.util.concurrent.CountDownLatch)1 AtomicReference (java.util.concurrent.atomic.AtomicReference)1 Test (org.junit.Test)1 TransactionIdentifier (org.opendaylight.controller.cluster.access.concepts.TransactionIdentifier)1 NoShardLeaderException (org.opendaylight.controller.cluster.datastore.exceptions.NoShardLeaderException)1 CanCommitTransaction (org.opendaylight.controller.cluster.datastore.messages.CanCommitTransaction)1 CommitTransaction (org.opendaylight.controller.cluster.datastore.messages.CommitTransaction)1 CommitTransactionReply (org.opendaylight.controller.cluster.datastore.messages.CommitTransactionReply)1 PrimaryShardInfo (org.opendaylight.controller.cluster.datastore.messages.PrimaryShardInfo)1 ReadyTransactionReply (org.opendaylight.controller.cluster.datastore.messages.ReadyTransactionReply)1 ElectionTimeout (org.opendaylight.controller.cluster.raft.base.messages.ElectionTimeout)1 DataValidationFailedException (org.opendaylight.yangtools.yang.data.api.schema.tree.DataValidationFailedException)1 FiniteDuration (scala.concurrent.duration.FiniteDuration)1