Expose completion future from WriteOperations 41/89141/19
authorRobert Varga <robert.varga@pantheon.tech>
Mon, 20 Apr 2020 09:35:37 +0000 (11:35 +0200)
committerRobert Varga <nite@hq.sk>
Sun, 14 Jan 2024 08:35:23 +0000 (08:35 +0000)
WriteOperations (and its counterparts) does not allow code to control
when a transaction is committed or aborted. This adds a layer of
separation, but unfortunately also prevents the encapsulated code from
reacting to when the changes are actually committed (or not).

This capability is quite important for chaining cache updates and
similar tasks.

Expose a FluentFuture<?>. which is guaranteed to complete when its
transaction is completed -- either successfully or not.

JIRA: MDSAL-61
Change-Id: Ie75671842b93fb9e63f1c2aa9ec72f25904da039
Signed-off-by: Robert Varga <robert.varga@pantheon.tech>
16 files changed:
binding/mdsal-binding-api/src/main/java/org/opendaylight/mdsal/binding/api/WriteOperations.java
binding/mdsal-binding-dom-adapter/src/main/java/org/opendaylight/mdsal/binding/dom/adapter/BindingDOMWriteTransactionAdapter.java
binding/mdsal-binding-spi/src/main/java/org/opendaylight/mdsal/binding/spi/ForwardingReadWriteTransaction.java
binding/mdsal-binding-spi/src/main/java/org/opendaylight/mdsal/binding/spi/ForwardingWriteTransaction.java
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TransactionAdapter.java
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransaction.java
binding/mdsal-binding-util/src/main/java/org/opendaylight/mdsal/binding/util/TypedWriteTransactionImpl.java
dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeWriteOperations.java
dom/mdsal-dom-api/src/main/java/org/opendaylight/mdsal/dom/api/DOMDataTreeWriteTransaction.java
dom/mdsal-dom-broker/src/main/java/org/opendaylight/mdsal/dom/broker/DOMForwardedWriteTransaction.java
dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/AbstractPingPongTransactionChain.java
dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/ForwardingDOMDataReadWriteTransaction.java
dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/ForwardingDOMDataWriteTransaction.java
dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/PingPongTransaction.java
dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/UncancellableListenableFuture.java [new file with mode: 0644]
trace/mdsal-trace-impl/src/main/java/org/opendaylight/mdsal/trace/impl/AbstractTracingWriteTransaction.java

index bd695d365961d06954186b51be07944bc519c3ee..b054d43f7bd0f14f9e8f19ef2122d9ca4510b4e1 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.mdsal.binding.api;
 
+import com.google.common.util.concurrent.FluentFuture;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.common.api.TransactionDatastoreMismatchException;
@@ -148,4 +149,11 @@ public interface WriteOperations {
      * @throws TransactionDatastoreMismatchException if this transaction is already bound to a different data store
      */
     void delete(@NonNull LogicalDatastoreType store, @NonNull InstanceIdentifier<?> path);
+
+    /**
+     * Return a {@link FluentFuture} which completes.
+     *
+     * @return A future which completes when the requested operations complete.
+     */
+    @NonNull FluentFuture<?> completionFuture();
 }
index 016b781d59ccfbc5c0f1a1f0111e9b07a9d50834..565f070f46723b77fb8807f669eb93093199e5ba 100644 (file)
@@ -133,6 +133,11 @@ class BindingDOMWriteTransactionAdapter<T extends DOMDataTreeWriteTransaction> e
         return getDelegate().cancel();
     }
 
+    @Override
+    public FluentFuture<?> completionFuture() {
+        return getDelegate().completionFuture();
+    }
+
     /**
      * Subclasses of this class are required to implement creation of parent nodes based on behaviour of their
      * underlying transaction.
index 8f4b12616dc83e83285baaad181d18668314fad8..31fc0204219340925cdae9eb74814370f675f4b7 100644 (file)
@@ -84,4 +84,9 @@ public class ForwardingReadWriteTransaction extends ForwardingTransaction implem
     public void delete(final LogicalDatastoreType store, final InstanceIdentifier<?> path) {
         delegate.delete(store, path);
     }
+
+    @Override
+    public FluentFuture<?> completionFuture() {
+        return delegate.completionFuture();
+    }
 }
index 4203d26515a23e8e976505444e77358c7e15a307..93378266aaced28c16f074ebe85a07e4c73c6ae7 100644 (file)
@@ -73,4 +73,9 @@ public class ForwardingWriteTransaction extends ForwardingTransaction implements
     public FluentFuture<? extends CommitInfo> commit() {
         return delegate.commit();
     }
+
+    @Override
+    public FluentFuture<?> completionFuture() {
+        return delegate.completionFuture();
+    }
 }
index 31419dcf93847466aa03883f69186918aa8fd598..0d0c5e7bc9d1e45c06d9d0457ec2791763c8f1e9 100644 (file)
@@ -127,6 +127,11 @@ public final class TransactionAdapter {
             return delegate.getIdentifier();
         }
 
+        @Override
+        public FluentFuture<?> completionFuture() {
+            return delegate.completionFuture();
+        }
+
         @Override
         protected D delegate() {
             return delegate;
index 7782e797127176eb2fa89418ce1f19ede7b9a5a4..97951a11497b9c5b93fd5ccf3264df4494ddc3cc 100644 (file)
@@ -8,6 +8,8 @@
 package org.opendaylight.mdsal.binding.util;
 
 import com.google.common.annotations.Beta;
+import com.google.common.util.concurrent.FluentFuture;
+import edu.umd.cs.findbugs.annotations.CheckReturnValue;
 import org.opendaylight.mdsal.binding.api.Transaction;
 import org.opendaylight.mdsal.binding.api.WriteOperations;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
@@ -79,4 +81,13 @@ public interface TypedWriteTransaction<D extends Datastore> extends Transaction
      * @param path The path to delete.
      */
     void delete(InstanceIdentifier<?> path);
+
+    /**
+     * Return a {@link FluentFuture} which completes.
+     *
+     * @return A future which completes when the requested operations complete.
+     */
+    @Beta
+    @CheckReturnValue
+    FluentFuture<?> completionFuture();
 }
index f5fa3e0700a349634108b45f4e5936be25b1e035..b872606308dc9bc0fc6c6f1b6aafd6565f068c92 100644 (file)
@@ -7,6 +7,7 @@
  */
 package org.opendaylight.mdsal.binding.util;
 
+import com.google.common.util.concurrent.FluentFuture;
 import org.opendaylight.mdsal.binding.api.WriteTransaction;
 import org.opendaylight.yangtools.yang.binding.DataObject;
 import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
@@ -55,6 +56,11 @@ class TypedWriteTransactionImpl<D extends Datastore, X extends WriteTransaction>
         postOperation();
     }
 
+    @Override
+    public final FluentFuture<?> completionFuture() {
+        return delegate().completionFuture();
+    }
+
     void postOperation() {
         // Defaults to no-op
     }
index 4422f25c6f1a80222479ba7c569e1d82b5847083..7bb139665ad0a2901ecf18e091c538bdae9a1899 100644 (file)
@@ -7,6 +7,10 @@
  */
 package org.opendaylight.mdsal.dom.api;
 
+import com.google.common.annotations.Beta;
+import com.google.common.util.concurrent.FluentFuture;
+import edu.umd.cs.findbugs.annotations.CheckReturnValue;
+import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.common.api.TransactionDatastoreMismatchException;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
@@ -59,4 +63,13 @@ public interface DOMDataTreeWriteOperations {
      * @throws TransactionDatastoreMismatchException if this transaction is already bound to a different data store
      */
     void delete(LogicalDatastoreType store, YangInstanceIdentifier path);
+
+    /**
+     * Return a {@link FluentFuture} which completes.
+     *
+     * @return A future which completes when the requested operations complete.
+     */
+    @Beta
+    @CheckReturnValue
+    @NonNull FluentFuture<?> completionFuture();
 }
index 9c06d7d589a1f2e23ba73bcb0c9e4a5383c3ab64..87036e1088c3a9194050bed5c0d38a2f53ac588e 100644 (file)
@@ -443,7 +443,7 @@ public interface DOMDataTreeWriteTransaction extends DOMDataTreeTransaction, DOM
 
     /**
      * Cancels the transaction. Transactions can only be cancelled if it was not yet committed.
-     * Invoking cancel() on failed or already canceled will have no effect, and transaction is considered cancelled.
+     * Invoking cancel() on failed or already cancelled will have no effect, and transaction is considered cancelled.
      * Invoking cancel() on finished transaction (future returned by {@link #commit()} already successfully completed)
      * will always fail (return false).
      *
index f105071113143317f7614419abdb657e04e99f3b..70b1e4043fb0e142eda63629144a6da4675838ca 100644 (file)
@@ -12,15 +12,16 @@ import static java.util.Objects.requireNonNull;
 import static org.opendaylight.yangtools.util.concurrent.FluentFutures.immediateFailedFluentFuture;
 
 import com.google.common.util.concurrent.FluentFuture;
-import com.google.common.util.concurrent.Futures;
-import java.util.concurrent.Future;
+import com.google.common.util.concurrent.SettableFuture;
 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
 import java.util.function.Function;
 import org.eclipse.jdt.annotation.NonNull;
 import org.opendaylight.mdsal.common.api.CommitInfo;
 import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
 import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
+import org.opendaylight.mdsal.dom.spi.UncancellableListenableFuture;
 import org.opendaylight.mdsal.dom.spi.store.DOMStoreWriteTransaction;
+import org.opendaylight.yangtools.util.concurrent.FluentFutures;
 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
 import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
 import org.slf4j.Logger;
@@ -50,10 +51,14 @@ class DOMForwardedWriteTransaction<T extends DOMStoreWriteTransaction>
         AbstractDOMForwardedTransactionFactory> IMPL_UPDATER = AtomicReferenceFieldUpdater.newUpdater(
                 DOMForwardedWriteTransaction.class, AbstractDOMForwardedTransactionFactory.class, "commitImpl");
     @SuppressWarnings("rawtypes")
-    private static final AtomicReferenceFieldUpdater<DOMForwardedWriteTransaction, Future> FUTURE_UPDATER =
-            AtomicReferenceFieldUpdater.newUpdater(DOMForwardedWriteTransaction.class, Future.class, "commitFuture");
+    private static final AtomicReferenceFieldUpdater<DOMForwardedWriteTransaction, FluentFuture> FUTURE_UPDATER =
+            AtomicReferenceFieldUpdater.newUpdater(DOMForwardedWriteTransaction.class, FluentFuture.class,
+                "commitFuture");
     private static final Logger LOG = LoggerFactory.getLogger(DOMForwardedWriteTransaction.class);
-    private static final Future<?> CANCELLED_FUTURE = Futures.immediateCancelledFuture();
+
+    private final @NonNull SettableFuture<@NonNull CommitInfo> settableCompletion = SettableFuture.create();
+    private final @NonNull FluentFuture<? extends @NonNull CommitInfo> completionFuture = FluentFuture.from(
+        new UncancellableListenableFuture<>(settableCompletion));
 
     /*
      * Implementation of real commit. It also acts as an indication that the transaction is running -- which we flip
@@ -69,7 +74,7 @@ class DOMForwardedWriteTransaction<T extends DOMStoreWriteTransaction>
      * busy-wait for it. The fast path gets the benefit of a store-store barrier instead of the usual store-load
      * barrier.
      */
-    private volatile Future<?> commitFuture;
+    private volatile FluentFuture<?> commitFuture;
 
     protected DOMForwardedWriteTransaction(final Object identifier,
             final Function<LogicalDatastoreType, T> backingTxFactory,
@@ -79,46 +84,47 @@ class DOMForwardedWriteTransaction<T extends DOMStoreWriteTransaction>
     }
 
     @Override
-    public void put(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode data) {
+    public final void put(final LogicalDatastoreType store, final YangInstanceIdentifier path,
+            final NormalizedNode data) {
         checkRunning(commitImpl);
         getSubtransaction(store).write(path, data);
     }
 
     @Override
-    public void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
+    public final void delete(final LogicalDatastoreType store, final YangInstanceIdentifier path) {
         checkRunning(commitImpl);
         getSubtransaction(store).delete(path);
     }
 
     @Override
-    public void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path, final NormalizedNode data) {
+    public final void merge(final LogicalDatastoreType store, final YangInstanceIdentifier path,
+            final NormalizedNode data) {
         checkRunning(commitImpl);
         getSubtransaction(store).merge(path, data);
     }
 
     @Override
-    public boolean cancel() {
+    public final boolean cancel() {
         final AbstractDOMForwardedTransactionFactory<?> impl = IMPL_UPDATER.getAndSet(this, null);
         if (impl != null) {
             LOG.trace("Transaction {} cancelled before submit", getIdentifier());
-            FUTURE_UPDATER.lazySet(this, CANCELLED_FUTURE);
+            FUTURE_UPDATER.lazySet(this, FluentFutures.immediateCancelledFluentFuture());
             closeSubtransactions();
             return true;
         }
 
-        // The transaction is in process of being submitted or cancelled. Busy-wait
-        // for the corresponding future.
-        Future<?> future;
+        // The transaction is in process of being submitted or cancelled. Busy-wait for the corresponding future.
+        FluentFuture<?> future;
         do {
             future = commitFuture;
         } while (future == null);
 
-        return future.cancel(false);
+        return future.isCancelled();
     }
 
     @Override
     @SuppressWarnings("checkstyle:IllegalCatch")
-    public @NonNull FluentFuture<? extends @NonNull CommitInfo> commit() {
+    public final FluentFuture<? extends CommitInfo> commit() {
         final AbstractDOMForwardedTransactionFactory<?> impl = IMPL_UPDATER.getAndSet(this, null);
         checkRunning(impl);
 
@@ -134,8 +140,14 @@ class DOMForwardedWriteTransaction<T extends DOMStoreWriteTransaction>
             }
         }
 
+        settableCompletion.setFuture(ret);
         FUTURE_UPDATER.lazySet(this, ret);
-        return ret;
+        return completionFuture;
+    }
+
+    @Override
+    public final FluentFuture<?> completionFuture() {
+        return completionFuture;
     }
 
     private void checkRunning(final AbstractDOMForwardedTransactionFactory<?> impl) {
index 3e8ee02e21edaf040d5d3607a036f9964b02ced2..ce4da1b258e8b509425179d31899ed13a4855628 100644 (file)
@@ -489,7 +489,7 @@ abstract class AbstractPingPongTransactionChain implements DOMTransactionChain {
         public FluentFuture<? extends CommitInfo> commit() {
             readyTransaction(tx);
             isOpen = false;
-            return tx.getCommitFuture().transform(ignored -> CommitInfo.empty(), MoreExecutors.directExecutor());
+            return tx.completionFuture();
         }
 
         @Override
index ea290a829c5a120eb8fade426d13414f6071d626..7fcd5125e6465660d1e999d13d0b3b80ab48dd75 100644 (file)
@@ -66,4 +66,9 @@ public abstract class ForwardingDOMDataReadWriteTransaction extends ForwardingOb
     public FluentFuture<? extends CommitInfo> commit() {
         return delegate().commit();
     }
+
+    @Override
+    public FluentFuture<?> completionFuture() {
+        return delegate().completionFuture();
+    }
 }
index 8e748e957e36d6cf3849849681a04faa7dc77a42..f32ac7fc39730f5cc94d90032ea89757c563300d 100644 (file)
@@ -50,6 +50,11 @@ public abstract class ForwardingDOMDataWriteTransaction extends ForwardingObject
         delegate().delete(store, path);
     }
 
+    @Override
+    public FluentFuture<?> completionFuture() {
+        return delegate().completionFuture();
+    }
+
     @Override
     public FluentFuture<? extends CommitInfo> commit() {
         return delegate().commit();
index b4472248acb1c6d74e28117d404b571891f34eca..f55d674be37766262382a6392759e5bb098f9e61 100644 (file)
@@ -25,7 +25,8 @@ import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
  */
 final class PingPongTransaction implements FutureCallback<CommitInfo> {
     private final @NonNull SettableFuture<CommitInfo> future = SettableFuture.create();
-    private final @NonNull FluentFuture<CommitInfo> fluent = FluentFuture.from(future);
+    private final @NonNull FluentFuture<CommitInfo> fluent =
+        FluentFuture.from(new UncancellableListenableFuture<>(future));
     private final @NonNull DOMDataTreeReadWriteTransaction delegate;
 
     private @Nullable DOMDataTreeReadWriteTransaction frontendTransaction;
@@ -42,7 +43,7 @@ final class PingPongTransaction implements FutureCallback<CommitInfo> {
         return frontendTransaction;
     }
 
-    @NonNull FluentFuture<CommitInfo> getCommitFuture() {
+    @NonNull FluentFuture<? extends CommitInfo> completionFuture() {
         return fluent;
     }
 
diff --git a/dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/UncancellableListenableFuture.java b/dom/mdsal-dom-spi/src/main/java/org/opendaylight/mdsal/dom/spi/UncancellableListenableFuture.java
new file mode 100644 (file)
index 0000000..d80634b
--- /dev/null
@@ -0,0 +1,29 @@
+/*
+ * Copyright (c) 2020 PANTHEON.tech, s.r.o. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.mdsal.dom.spi;
+
+import com.google.common.annotations.Beta;
+import com.google.common.util.concurrent.ForwardingListenableFuture.SimpleForwardingListenableFuture;
+import com.google.common.util.concurrent.ListenableFuture;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+@Beta
+public final class UncancellableListenableFuture<V> extends SimpleForwardingListenableFuture<V> {
+    private static final Logger LOG = LoggerFactory.getLogger(UncancellableListenableFuture.class);
+
+    public UncancellableListenableFuture(final ListenableFuture<V> delegate) {
+        super(delegate);
+    }
+
+    @Override
+    public boolean cancel(final boolean mayInterruptIfRunning) {
+        LOG.debug("Attempted to cancel future", new Throwable());
+        return false;
+    }
+}
index 6982302cde2d39f47abcb213e54c2f70506819a3..1955fb772ca105a2b852042e840e02a72bc1dfa7 100644 (file)
@@ -106,6 +106,11 @@ abstract class AbstractTracingWriteTransaction implements DOMDataTreeWriteTransa
         return delegate.getIdentifier();
     }
 
+    @Override
+    public final FluentFuture<?> completionFuture() {
+        return delegate.completionFuture();
+    }
+
     // https://jira.opendaylight.org/browse/CONTROLLER-1792
 
     @Override