+ private void checkStatusAndMakeToast(final MakeToastInput input,
+ final SettableFuture<RpcResult<MakeToastOutput>> futureResult, final int tries) {
+ // Read the ToasterStatus and, if currently Up, try to write the status to Down.
+ // If that succeeds, then we essentially have an exclusive lock and can proceed
+ // to make toast.
+ final ReadWriteTransaction tx = dataBroker.newReadWriteTransaction();
+ FluentFuture<Optional<Toaster>> readFuture = tx.read(OPERATIONAL, TOASTER_IID);
+
+ final ListenableFuture<? extends CommitInfo> commitFuture =
+ Futures.transformAsync(readFuture, toasterData -> {
+ ToasterStatus toasterStatus = ToasterStatus.Up;
+ if (toasterData.isPresent()) {
+ toasterStatus = toasterData.orElseThrow().getToasterStatus();
+ }
+
+ LOG.debug("Read toaster status: {}", toasterStatus);
+
+ if (toasterStatus == ToasterStatus.Up) {
+
+ if (outOfBread()) {
+ LOG.debug("Toaster is out of bread");
+ tx.cancel();
+ return Futures.immediateFailedFuture(
+ new TransactionCommitFailedException("", makeToasterOutOfBreadError()));
+ }
+
+ LOG.debug("Setting Toaster status to Down");
+
+ // We're not currently making toast - try to update the status to Down
+ // to indicate we're going to make toast. This acts as a lock to prevent
+ // concurrent toasting.
+ tx.put(OPERATIONAL, TOASTER_IID, buildToaster(ToasterStatus.Down));
+ return tx.commit();
+ }
+
+ LOG.debug("Oops - already making toast!");
+
+ // Return an error since we are already making toast. This will get
+ // propagated to the commitFuture below which will interpret the null
+ // TransactionStatus in the RpcResult as an error condition.
+ tx.cancel();
+ return Futures.immediateFailedFuture(
+ new TransactionCommitFailedException("", makeToasterInUseError()));
+ }, MoreExecutors.directExecutor());
+
+ Futures.addCallback(commitFuture, new FutureCallback<CommitInfo>() {
+ @Override
+ public void onSuccess(final CommitInfo result) {
+ // OK to make toast
+ currentMakeToastTask.set(executor.submit(new MakeToastTask(input, futureResult)));
+ }
+
+ @Override
+ public void onFailure(final Throwable ex) {
+ if (ex instanceof OptimisticLockFailedException) {
+
+ // Another thread is likely trying to make toast simultaneously and updated the
+ // status before us. Try reading the status again - if another make toast is
+ // now in progress, we should get ToasterStatus.Down and fail.
+
+ if (tries - 1 > 0) {
+ LOG.debug("Got OptimisticLockFailedException - trying again");
+ checkStatusAndMakeToast(input, futureResult, tries - 1);
+ } else {
+ futureResult.set(RpcResultBuilder.<MakeToastOutput>failed()
+ .withError(ErrorType.APPLICATION, ex.getMessage()).build());
+ }
+ } else if (ex instanceof TransactionCommitFailedException) {
+ LOG.debug("Failed to commit Toaster status", ex);
+
+ // Probably already making toast.
+ futureResult.set(RpcResultBuilder.<MakeToastOutput>failed()
+ .withRpcErrors(((TransactionCommitFailedException)ex).getErrorList()).build());
+ } else {
+ LOG.debug("Unexpected error committing Toaster status", ex);
+ futureResult.set(RpcResultBuilder.<MakeToastOutput>failed().withError(ErrorType.APPLICATION,
+ "Unexpected error committing Toaster status", ex).build());
+ }
+ }
+ }, MoreExecutors.directExecutor());