+++ /dev/null
-/*
- * Copyright (c) 2014 Cisco Systems, Inc. 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.binding.api;
-
-import org.opendaylight.mdsal.common.api.TransactionChain;
-import org.opendaylight.yangtools.yang.binding.DataObject;
-import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
-
-/**
- * A chain of transactions.
- *
- * <p>
- * For more information about transaction chaining and transaction chains
- * see {@link TransactionChain}.
- *
- * @see TransactionChain
- *
- */
-public interface BindingTransactionChain extends TransactionFactory,
- TransactionChain<InstanceIdentifier<?>, DataObject> {
-
- @Override
- ReadTransaction newReadOnlyTransaction();
-
- @Override
- WriteTransaction newWriteOnlyTransaction();
-
- @Override
- ReadWriteTransaction newReadWriteTransaction();
-}
package org.opendaylight.mdsal.binding.api;
import org.opendaylight.mdsal.common.api.AsyncDataBroker;
-import org.opendaylight.mdsal.common.api.TransactionChainFactory;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.yangtools.yang.binding.DataObject;
import org.opendaylight.yangtools.yang.binding.InstanceIdentifier;
* <b>Implementation Note:</b> This interface is not intended to be implemented by users of MD-SAL,
* but only to be consumed by them.
*/
-public interface DataBroker extends AsyncDataBroker<InstanceIdentifier<?>, DataObject>,
- TransactionChainFactory<InstanceIdentifier<?>, DataObject>, TransactionFactory, BindingService,
- DataTreeChangeService {
-
- @Override
- BindingTransactionChain createTransactionChain(TransactionChainListener listener);
+public interface DataBroker extends AsyncDataBroker<InstanceIdentifier<?>, DataObject>, TransactionFactory,
+ BindingService, DataTreeChangeService {
+ /**
+ * Create a new transaction chain. The chain will be initialized to read from its backing datastore, with
+ * no outstanding transaction. Listener will be registered to handle chain-level events.
+ *
+ * @param listener Transaction chain event listener
+ * @return A new transaction chain.
+ */
+ TransactionChain createTransactionChain(TransactionChainListener listener);
}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. 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.binding.api;
+
+import org.opendaylight.yangtools.concepts.Registration;
+
+/**
+ * A chain of transactions. Transactions in a chain need to be committed in sequence and each transaction should see
+ * the effects of previous committed transactions as they occurred. A chain makes no guarantees of atomicity across
+ * the chained transactions - the transactions are committed as soon as possible in the order that they were committed.
+ * This behaviour is different from the default AsyncDataBroker, where a transaction is always created from the current
+ * global state, not taking into account any transactions previously committed by the calling thread. Due to
+ * the asynchronous nature of transaction submission this can lead to surprising results. If a thread executes
+ * the following sequence sufficiently quickly:
+ *
+ * <code>
+ * AsyncWriteTransaction t1 = broker.newWriteOnlyTransaction();
+ * t1.put(id, data);
+ * t1.commit();
+ *
+ * AsyncReadTransaction t2 = broker.newReadOnlyTransaction();
+ * Optional<?> maybeData = t2.read(id).get();
+ * </code>
+ * it may happen, that it sees maybeData.isPresent() == false, simply because t1 has not completed the processes
+ * of being applied and t2 is actually allocated from the previous state. This is obviously bad for users who create
+ * incremental state in the datastore and actually read what they write in subsequent transactions. Using
+ * a TransactionChain instead of a broker solves this particular problem, and leads to expected behavior: t2 will always
+ * see the data written in t1 present.
+ */
+public interface TransactionChain extends Registration, TransactionFactory {
+ /**
+ * Create a new read only transaction which will continue the chain.
+ *
+ * <p>
+ * The previous write transaction has to be either SUBMITTED ({@link WriteTransaction#commit commit} was invoked)
+ * or CANCELLED ({@link #close close} was invoked).
+ *
+ * <p>
+ * The returned read-only transaction presents an isolated view of the data if the previous write transaction was
+ * successful - in other words, this read-only transaction will see the state changes made by the previous write
+ * transaction in the chain. However, state which was introduced by other transactions outside this transaction
+ * chain after creation of the previous transaction is not visible.
+ *
+ * @return New transaction in the chain.
+ * @throws IllegalStateException if the previous transaction was not SUBMITTED or CANCELLED.
+ * @throws TransactionChainClosedException if the chain has been closed.
+ */
+ @Override
+ ReadTransaction newReadOnlyTransaction();
+
+ /**
+ * Create a new write-only transaction which will continue the chain.
+ *
+ * <p>
+ * The previous write transaction has to be either SUBMITTED ({@link WriteTransaction#commit commit} was invoked)
+ * or CANCELLED ({@link #close close} was invoked)
+ *
+ * <p>
+ * The returned write-only transaction presents an isolated view of the data if the previous write transaction was
+ * successful - in other words, this write-only transaction will see the state changes made by the previous write
+ * transaction in the chain. However, state which was introduced by other transactions outside this transaction
+ * chain after creation of the previous transaction is not visible
+ *
+ * <p>
+ * Committing this write-only transaction using {@link WriteTransaction#commit commit} will commit the state
+ * changes in this transaction to be visible to any subsequent transaction in this chain and also to any transaction
+ * outside this chain.
+ *
+ * @return New transaction in the chain.
+ * @throws IllegalStateException if the previous transaction was not SUBMITTED or CANCELLED.
+ * @throws TransactionChainClosedException if the chain has been closed.
+ */
+ @Override
+ WriteTransaction newWriteOnlyTransaction();
+
+ /**
+ * Create a new read-write transaction which will continue the chain.
+ *
+ * <p>
+ * The previous write transaction has to be either SUBMITTED ({@link WriteTransaction#commit commit} was invoked)
+ * or CANCELLED ({@link #close close} was invoked).
+ *
+ * <p>
+ * The returned read-write transaction presents an isolated view of the data if the previous write transaction was
+ * successful - in other words, this read-write transaction will see the state changes made by the previous write
+ * transaction in the chain. However, state which was introduced by other transactions outside this transaction
+ * chain after creation of the previous transaction is not visible.
+ *
+ * <p>
+ * Committing this read-write transaction using {@link WriteTransaction#commit commit} will commit the state changes
+ * in this transaction to be visible to any subsequent transaction in this chain and also to any transaction outside
+ * this chain.
+ *
+ * @return New transaction in the chain.
+ * @throws IllegalStateException if the previous transaction was not SUBMITTED or CANCELLED.
+ * @throws TransactionChainClosedException if the chain has been closed.
+ */
+ @Override
+ ReadWriteTransaction newReadWriteTransaction();
+}
* 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.common.api;
+package org.opendaylight.mdsal.binding.api;
/**
* Exception thrown when an attempt is made to open a new transaction in a closed
* 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.common.api;
+package org.opendaylight.mdsal.binding.api;
import java.util.EventListener;
/**
* Listener for transaction chain events.
*/
+// FIXME: 4.0.0: remove this in favor of a TransactionChain destiny, available as a FluentFuture from TransactionChain
public interface TransactionChainListener extends EventListener {
/**
* Invoked if when a transaction in the chain fails. All transactions submitted after the failed transaction, in the
* @param transaction Transaction which caused the chain to fail
* @param cause The cause of transaction failure
*/
- void onTransactionChainFailed(TransactionChain<?, ?> chain, AsyncTransaction<?, ?> transaction, Throwable cause);
+ void onTransactionChainFailed(TransactionChain chain, Transaction transaction, Throwable cause);
/**
* Invoked when a transaction chain is completed. A transaction chain is considered completed when it has been
*
* @param chain Transaction chain which completed
*/
- void onTransactionChainSuccessful(TransactionChain<?, ?> chain);
+ void onTransactionChainSuccessful(TransactionChain chain);
}
* 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.common.api;
+package org.opendaylight.mdsal.binding.api;
import org.junit.Test;
import com.google.common.collect.ClassToInstanceMap;
import com.google.common.collect.ImmutableSet;
import java.util.Set;
-import org.opendaylight.mdsal.binding.api.BindingTransactionChain;
import org.opendaylight.mdsal.binding.api.DataBroker;
import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
import org.opendaylight.mdsal.binding.api.DataTreeChangeService;
import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
import org.opendaylight.mdsal.binding.api.ReadTransaction;
import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.opendaylight.mdsal.binding.api.TransactionChain;
+import org.opendaylight.mdsal.binding.api.TransactionChainListener;
import org.opendaylight.mdsal.binding.api.WriteTransaction;
import org.opendaylight.mdsal.binding.dom.adapter.BindingDOMAdapterBuilder.Factory;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
import org.opendaylight.mdsal.dom.api.DOMService;
}
@Override
- public BindingTransactionChain createTransactionChain(final TransactionChainListener listener) {
+ public TransactionChain createTransactionChain(final TransactionChainListener listener) {
return new BindingDOMTransactionChainAdapter(getDelegate(), getCodec(), listener);
}
*/
package org.opendaylight.mdsal.binding.dom.adapter;
-import com.google.common.base.Preconditions;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
+import java.util.function.Supplier;
import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.mdsal.binding.api.BindingTransactionChain;
import org.opendaylight.mdsal.binding.api.ReadTransaction;
import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.opendaylight.mdsal.binding.api.TransactionChain;
+import org.opendaylight.mdsal.binding.api.TransactionChainClosedException;
+import org.opendaylight.mdsal.binding.api.TransactionChainListener;
import org.opendaylight.mdsal.binding.api.WriteTransaction;
-import org.opendaylight.mdsal.common.api.AsyncTransaction;
import org.opendaylight.mdsal.common.api.CommitInfo;
-import org.opendaylight.mdsal.common.api.TransactionChain;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainClosedException;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
import org.opendaylight.yangtools.concepts.Delegator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
-final class BindingDOMTransactionChainAdapter implements BindingTransactionChain, Delegator<DOMTransactionChain> {
+final class BindingDOMTransactionChainAdapter implements TransactionChain, Delegator<DOMTransactionChain> {
private static final Logger LOG = LoggerFactory.getLogger(BindingDOMTransactionChainAdapter.class);
BindingDOMTransactionChainAdapter(final DOMDataBroker chainFactory,
final BindingToNormalizedNodeCodec codec, final TransactionChainListener listener) {
- Preconditions.checkNotNull(chainFactory, "DOM Transaction chain factory must not be null");
+ requireNonNull(chainFactory, "DOM Transaction chain factory must not be null");
this.domListener = new DelegateChainListener();
this.bindingListener = listener;
this.delegate = chainFactory.createTransactionChain(domListener);
@Override
public ReadTransaction newReadOnlyTransaction() {
- final DOMDataTreeReadTransaction delegateTx = delegate.newReadOnlyTransaction();
- return new BindingDOMReadTransactionAdapter(delegateTx, codec);
+ return new BindingDOMReadTransactionAdapter(createTransaction(delegate::newReadOnlyTransaction), codec);
}
@Override
public WriteTransaction newWriteOnlyTransaction() {
- final DOMDataTreeWriteTransaction delegateTx = delegate.newWriteOnlyTransaction();
+ final DOMDataTreeWriteTransaction delegateTx = createTransaction(delegate::newWriteOnlyTransaction);
return new BindingDOMWriteTransactionAdapter<DOMDataTreeWriteTransaction>(delegateTx, codec) {
-
@Override
public @NonNull FluentFuture<? extends @NonNull CommitInfo> commit() {
return listenForFailure(this, super.commit());
}
-
};
}
@Override
public ReadWriteTransaction newReadWriteTransaction() {
- final DOMDataTreeReadWriteTransaction delegateTx = delegate.newReadWriteTransaction();
+ final DOMDataTreeReadWriteTransaction delegateTx = createTransaction(delegate::newReadWriteTransaction);
return new BindingDOMReadWriteTransactionAdapter(delegateTx, codec) {
-
@Override
public @NonNull FluentFuture<? extends @NonNull CommitInfo> commit() {
return listenForFailure(this, super.commit());
delegate.close();
}
- private final class DelegateChainListener implements TransactionChainListener {
+ private static <T> T createTransaction(final Supplier<T> supplier) {
+ try {
+ return supplier.get();
+ } catch (DOMTransactionChainClosedException e) {
+ throw new TransactionChainClosedException("Transaction chain already closed", e);
+ }
+ }
+ private final class DelegateChainListener implements DOMTransactionChainListener {
@Override
- public void onTransactionChainFailed(final TransactionChain<?, ?> chain,
- final AsyncTransaction<?, ?> transaction, final Throwable cause) {
- Preconditions.checkState(delegate.equals(chain),
- "Illegal state - listener for %s was invoked for incorrect chain %s.", delegate, chain);
+ public void onTransactionChainFailed(final DOMTransactionChain chain, final DOMDataTreeTransaction transaction,
+ final Throwable cause) {
+ checkState(delegate.equals(chain), "Listener for %s was invoked for incorrect chain %s.", delegate, chain);
/*
* Intentionally NOOP, callback for failure, since we
* are also listening on each transaction future for failure,
}
@Override
- public void onTransactionChainSuccessful(final TransactionChain<?, ?> chain) {
- Preconditions.checkState(delegate.equals(chain),
- "Illegal state - listener for %s was invoked for incorrect chain %s.", delegate, chain);
+ public void onTransactionChainSuccessful(final DOMTransactionChain chain) {
+ checkState(delegate.equals(chain), "Listener for %s was invoked for incorrect chain %s.", delegate, chain);
bindingListener.onTransactionChainSuccessful(BindingDOMTransactionChainAdapter.this);
}
}
import com.google.common.collect.ForwardingObject;
import javax.annotation.Nonnull;
-import org.opendaylight.mdsal.binding.api.BindingTransactionChain;
import org.opendaylight.mdsal.binding.api.DataBroker;
import org.opendaylight.mdsal.binding.api.DataTreeChangeListener;
import org.opendaylight.mdsal.binding.api.DataTreeIdentifier;
import org.opendaylight.mdsal.binding.api.ReadTransaction;
import org.opendaylight.mdsal.binding.api.ReadWriteTransaction;
+import org.opendaylight.mdsal.binding.api.TransactionChain;
+import org.opendaylight.mdsal.binding.api.TransactionChainListener;
import org.opendaylight.mdsal.binding.api.WriteTransaction;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
import org.opendaylight.yangtools.yang.binding.DataObject;
@Override
public <T extends DataObject, L extends DataTreeChangeListener<T>> ListenerRegistration<L>
- registerDataTreeChangeListener(DataTreeIdentifier<T> treeId, L listener) {
+ registerDataTreeChangeListener(final DataTreeIdentifier<T> treeId, final L listener) {
return delegate().registerDataTreeChangeListener(treeId, listener);
}
@Override
- public BindingTransactionChain createTransactionChain(TransactionChainListener listener) {
+ public TransactionChain createTransactionChain(final TransactionChainListener listener) {
return delegate().createTransactionChain(listener);
}
+++ /dev/null
-/*
- * Copyright (c) 2017 Pantheon Technologies 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.binding.javav2.api;
-
-import com.google.common.annotations.Beta;
-import org.opendaylight.mdsal.binding.javav2.spec.base.InstanceIdentifier;
-import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
-import org.opendaylight.mdsal.common.api.TransactionChain;
-
-/**
- * A chain of transactions.
- *
- * <p>
- * For more information about transaction chaining and transaction chains
- * see {@link TransactionChain}.
- *
- * @see TransactionChain
- *
- */
-@Beta
-public interface BindingTransactionChain extends TransactionFactory, TransactionChain<InstanceIdentifier<?>, TreeNode> {
-
- @Override
- ReadTransaction newReadOnlyTransaction();
-
- @Override
- WriteTransaction newWriteOnlyTransaction();
-}
import org.opendaylight.mdsal.binding.javav2.spec.base.InstanceIdentifier;
import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
import org.opendaylight.mdsal.common.api.AsyncDataBroker;
-import org.opendaylight.mdsal.common.api.TransactionChainFactory;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
/**
* Provides access to a conceptual data tree store and also provides the ability to
* subscribe for changes to data under a given branch of the tree.
*
* <p>
- * For more information on usage, please see the documentation in {@link AsyncDataBroker}.
+ * All operations on the data tree are performed via one of the transactions:
+ * <ul>
+ * <li>Read-Only - allocated using {@link #newReadOnlyTransaction()}
+ * <li>Write-Only - allocated using {@link #newWriteOnlyTransaction()}
+ * </ul>
*
- * @see AsyncDataBroker
- * @see TransactionChainFactory
+ * <p>
+ * These transactions provide a stable isolated view of data tree, which is guaranteed to be not
+ * affected by other concurrent transactions, until transaction is committed.
+ *
+ * <p>
+ * For a detailed explanation of how transaction are isolated and how transaction-local changes are
+ * committed to global data tree, see {@link ReadTransaction}, {@link WriteTransaction}
+ * and {@link WriteTransaction#commit()}.
+ *
+ * <p>
+ * It is strongly recommended to use the type of transaction, which provides only the minimal
+ * capabilities you need. This allows for optimizations at the data broker / data store level. For
+ * example, implementations may optimize the transaction for reading if they know ahead of time that
+ * you only need to read data - such as not keeping additional meta-data, which may be required for
+ * write transactions.
+ *
+ * <p>
+ * <b>Implementation Note:</b> This interface is not intended to be implemented by users of MD-SAL,
+ * but only to be consumed by them.
*/
@Beta
public interface DataBroker extends AsyncDataBroker<InstanceIdentifier<?>, TreeNode>, BindingService,
- TransactionFactory, DataTreeService, TransactionChainFactory<InstanceIdentifier<?>, TreeNode> {
-
- @Override
- BindingTransactionChain createTransactionChain(TransactionChainListener listener);
+ TransactionFactory, DataTreeService {
+ /**
+ * Create a new transaction chain. The chain will be initialized to read from its backing datastore, with
+ * no outstanding transaction. Listener will be registered to handle chain-level events.
+ *
+ * @param listener Transaction chain event listener
+ * @return A new transaction chain.
+ */
+ TransactionChain createTransactionChain(TransactionChainListener listener);
}
* {@link org.opendaylight.mdsal.common.api.AsyncReadTransaction}.
*/
@Beta
-public interface ReadTransaction extends AsyncReadTransaction<InstanceIdentifier<?>, TreeNode> {
+public interface ReadTransaction extends Transaction, AsyncReadTransaction<InstanceIdentifier<?>, TreeNode> {
/**
* Reads data from the provided logical data store located at the provided path.
--- /dev/null
+/*
+ * Copyright (c) 2018 Pantheon Technologies, 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.binding.javav2.api;
+
+import org.opendaylight.mdsal.binding.javav2.spec.base.InstanceIdentifier;
+import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
+import org.opendaylight.mdsal.common.api.AsyncTransaction;
+
+/**
+ * A common parent for all transactions which operate on a conceptual data tree.
+ * See derived transaction types for more concrete behavior:
+ * <ul>
+ * <li>{@link ReadTransaction} - Read capabilities, user is able to read data from data tree</li>
+ * <li>{@link WriteTransaction} - Write capabilities, user is able to propose changes to data tree</li>
+ * </ul>
+ *
+ * <b>Implementation Note:</b> This interface is not intended to be implemented by users of MD-SAL.
+ */
+public interface Transaction extends AsyncTransaction<InstanceIdentifier<?>, TreeNode> {
+ @Override
+ Object getIdentifier();
+}
--- /dev/null
+/*
+ * Copyright (c) 2017 Pantheon Technologies 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.binding.javav2.api;
+
+import com.google.common.annotations.Beta;
+import org.opendaylight.yangtools.concepts.Registration;
+
+/**
+ * A chain of transactions. Transactions in a chain need to be committed in sequence and each transaction should see
+ * the effects of previous committed transactions as they occurred. A chain makes no guarantees of atomicity across
+ * the chained transactions - the transactions are committed as soon as possible in the order that they were committed.
+ * This behaviour is different from the default AsyncDataBroker, where a transaction is always created from the current
+ * global state, not taking into account any transactions previously committed by the calling thread. Due to
+ * the asynchronous nature of transaction submission this can lead to surprising results. If a thread executes
+ * the following sequence sufficiently quickly:
+ *
+ * <code>
+ * AsyncWriteTransaction t1 = broker.newWriteOnlyTransaction();
+ * t1.put(id, data);
+ * t1.commit();
+ *
+ * AsyncReadTransaction t2 = broker.newReadOnlyTransaction();
+ * Optional<?> maybeData = t2.read(id).get();
+ * </code>
+ * it may happen, that it sees maybeData.isPresent() == false, simply because t1 has not completed the processes
+ * of being applied and t2 is actually allocated from the previous state. This is obviously bad for users who create
+ * incremental state in the datastore and actually read what they write in subsequent transactions. Using
+ * a TransactionChain instead of a broker solves this particular problem, and leads to expected behavior: t2 will always
+ * see the data written in t1 present.
+ */
+@Beta
+public interface TransactionChain extends Registration, TransactionFactory {
+ /**
+ * Create a new read only transaction which will continue the chain.
+ *
+ * <p>
+ * The previous write transaction has to be either SUBMITTED ({@link WriteTransaction#commit commit} was invoked)
+ * or CANCELLED ({@link #close close} was invoked).
+ *
+ * <p>
+ * The returned read-only transaction presents an isolated view of the data if the previous write transaction was
+ * successful - in other words, this read-only transaction will see the state changes made by the previous write
+ * transaction in the chain. However, state which was introduced by other transactions outside this transaction
+ * chain after creation of the previous transaction is not visible.
+ *
+ * @return New transaction in the chain.
+ * @throws IllegalStateException if the previous transaction was not SUBMITTED or CANCELLED.
+ * @throws TransactionChainClosedException if the chain has been closed.
+ */
+ @Override
+ ReadTransaction newReadOnlyTransaction();
+
+ /**
+ * Create a new write-only transaction which will continue the chain.
+ *
+ * <p>
+ * The previous write transaction has to be either SUBMITTED ({@link WriteTransaction#commit commit} was invoked)
+ * or CANCELLED ({@link #close close} was invoked)
+ *
+ * <p>
+ * The returned write-only transaction presents an isolated view of the data if the previous write transaction was
+ * successful - in other words, this write-only transaction will see the state changes made by the previous write
+ * transaction in the chain. However, state which was introduced by other transactions outside this transaction
+ * chain after creation of the previous transaction is not visible
+ *
+ * <p>
+ * Committing this write-only transaction using {@link WriteTransaction#commit commit} will commit the state
+ * changes in this transaction to be visible to any subsequent transaction in this chain and also to any transaction
+ * outside this chain.
+ *
+ * @return New transaction in the chain.
+ * @throws IllegalStateException if the previous transaction was not SUBMITTED or CANCELLED.
+ * @throws TransactionChainClosedException if the chain has been closed.
+ */
+ @Override
+ WriteTransaction newWriteOnlyTransaction();
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Pantheon Technologies, 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.binding.javav2.api;
+
+/**
+ * Exception thrown when an attempt is made to open a new transaction in a closed chain.
+ */
+public final class TransactionChainClosedException extends IllegalStateException {
+ private static final long serialVersionUID = 1L;
+
+ public TransactionChainClosedException(final String message) {
+ super(message);
+ }
+
+ public TransactionChainClosedException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2014 Cisco Systems, Inc. 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.binding.javav2.api;
+
+import java.util.EventListener;
+
+/**
+ * Listener for transaction chain events.
+ */
+// FIXME: 4.0.0: remove this in favor of a TransactionChain destiny, available as a FluentFuture from TransactionChain
+public interface TransactionChainListener extends EventListener {
+ /**
+ * Invoked if when a transaction in the chain fails. All transactions submitted after the failed transaction, in the
+ * chain, are automatically cancelled by the time this notification is invoked. Open transactions need to be closed
+ * or cancelled.
+ * Implementations should invoke chain.close() to close the chain.
+ *
+ * @param chain Transaction chain which failed
+ * @param transaction Transaction which caused the chain to fail
+ * @param cause The cause of transaction failure
+ */
+ void onTransactionChainFailed(TransactionChain chain, Transaction transaction, Throwable cause);
+
+ /**
+ * Invoked when a transaction chain is completed. A transaction chain is considered completed when it has been
+ * closed and all its instructions have completed successfully.
+ *
+ * @param chain Transaction chain which completed
+ */
+ void onTransactionChainSuccessful(TransactionChain chain);
+}
+
* 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.binding.javav2.api;
import com.google.common.annotations.Beta;
* A transaction that provides mutation capabilities on a data tree.
*
* <p>
- * For more information on usage and examples, please see the documentation in {@link AsyncWriteTransaction}.
+ * Initial state of write transaction is a stable snapshot of the current data tree. The state is captured when
+ * the transaction is created and its state and underlying data tree are not affected by other concurrently running
+ * transactions.
+ *
+ * <p>
+ * Write transactions are isolated from other concurrent write transactions. All writes are local to the transaction
+ * and represent only a proposal of state change for the data tree and it is not visible to any other concurrently
+ * running transaction.
+ *
+ * <p>
+ * Applications make changes to the local data tree in the transaction by via the <b>put</b>, <b>merge</b>,
+ * and <b>delete</b> operations.
+ *
+ * <h2>Put operation</h2>
+ * Stores a piece of data at a specified path. This acts as an add / replace operation, which is to say that whole
+ * subtree will be replaced by the specified data.
+ *
+ * <p>
+ * Performing the following put operations:
+ *
+ * <pre>
+ * 1) container { list [ a ] }
+ * 2) container { list [ b ] }
+ * </pre>
+ * will result in the following data being present:
+ *
+ * <pre>
+ * container { list [ b ] }
+ * </pre>
+ * <h2>Merge operation</h2>
+ * Merges a piece of data with the existing data at a specified path. Any pre-existing data which is not explicitly
+ * overwritten will be preserved. This means that if you store a container, its child lists will be merged.
+ *
+ * <p>
+ * Performing the following merge operations:
+ *
+ * <pre>
+ * 1) container { list [ a ] }
+ * 2) container { list [ b ] }
+ * </pre>
+ * will result in the following data being present:
+ *
+ * <pre>
+ * container { list [ a, b ] }
+ * </pre>
+ * This also means that storing the container will preserve any augmentations which have been attached to it.
+ *
+ * <h2>Delete operation</h2>
+ * Removes a piece of data from a specified path.
+ *
+ * <p>
+ * After applying changes to the local data tree, applications publish the changes proposed in the transaction
+ * by calling {@link #commit} on the transaction. This seals the transaction (preventing any further writes using this
+ * transaction) and commits it to be processed and applied to global conceptual data tree.
+ *
+ * <p>
+ * The transaction commit may fail due to a concurrent transaction modifying and committing data in an incompatible way.
+ * See {@link #commit} for more concrete commit failure examples.
+ *
+ * <p>
+ * <b>Implementation Note:</b> This interface is not intended to be implemented by users of MD-SAL, but only to be
+ * consumed by them.
*/
@Beta
-public interface WriteTransaction extends AsyncWriteTransaction<InstanceIdentifier<?>, TreeNode> {
+public interface WriteTransaction extends AsyncWriteTransaction<InstanceIdentifier<?>, TreeNode>, Transaction {
@Override
boolean cancel();
import java.util.Collection;
import java.util.Set;
import javax.annotation.Nonnull;
-import org.opendaylight.mdsal.binding.javav2.api.BindingTransactionChain;
import org.opendaylight.mdsal.binding.javav2.api.DataBroker;
import org.opendaylight.mdsal.binding.javav2.api.DataTreeIdentifier;
import org.opendaylight.mdsal.binding.javav2.api.DataTreeListener;
import org.opendaylight.mdsal.binding.javav2.api.DataTreeProducer;
import org.opendaylight.mdsal.binding.javav2.api.DataTreeService;
import org.opendaylight.mdsal.binding.javav2.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.javav2.api.TransactionChain;
+import org.opendaylight.mdsal.binding.javav2.api.TransactionChainListener;
import org.opendaylight.mdsal.binding.javav2.api.WriteTransaction;
import org.opendaylight.mdsal.binding.javav2.dom.adapter.impl.data.tree.BindingDOMDataTreeServiceAdapter;
import org.opendaylight.mdsal.binding.javav2.dom.adapter.impl.transaction.BindingDOMReadTransactionAdapter;
import org.opendaylight.mdsal.binding.javav2.spec.base.InstanceIdentifier;
import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
import org.opendaylight.mdsal.common.api.AsyncReadWriteTransaction;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataTreeService;
import org.opendaylight.mdsal.dom.api.DOMService;
}
@Override
- public BindingTransactionChain createTransactionChain(final TransactionChainListener listener) {
+ public TransactionChain createTransactionChain(final TransactionChainListener listener) {
return new BindingDOMTransactionChainAdapter(getDelegate(), getCodec(), listener);
}
*/
package org.opendaylight.mdsal.binding.javav2.dom.adapter.impl.transaction;
+import static com.google.common.base.Preconditions.checkState;
+import static java.util.Objects.requireNonNull;
+
import com.google.common.annotations.Beta;
-import com.google.common.base.Preconditions;
import com.google.common.util.concurrent.FluentFuture;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.MoreExecutors;
+import java.util.function.Supplier;
import org.eclipse.jdt.annotation.NonNull;
-import org.opendaylight.mdsal.binding.javav2.api.BindingTransactionChain;
import org.opendaylight.mdsal.binding.javav2.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.javav2.api.TransactionChain;
+import org.opendaylight.mdsal.binding.javav2.api.TransactionChainClosedException;
+import org.opendaylight.mdsal.binding.javav2.api.TransactionChainListener;
import org.opendaylight.mdsal.binding.javav2.api.WriteTransaction;
import org.opendaylight.mdsal.binding.javav2.dom.codec.impl.BindingToNormalizedNodeCodec;
import org.opendaylight.mdsal.binding.javav2.spec.base.InstanceIdentifier;
import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
import org.opendaylight.mdsal.common.api.AsyncReadWriteTransaction;
-import org.opendaylight.mdsal.common.api.AsyncTransaction;
import org.opendaylight.mdsal.common.api.CommitInfo;
-import org.opendaylight.mdsal.common.api.TransactionChain;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
-import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainClosedException;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
import org.opendaylight.yangtools.concepts.Delegator;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
* Transaction chain adapter.
*/
@Beta
-public final class BindingDOMTransactionChainAdapter
- implements BindingTransactionChain, Delegator<DOMTransactionChain> {
+public final class BindingDOMTransactionChainAdapter implements TransactionChain, Delegator<DOMTransactionChain> {
private static final Logger LOG = LoggerFactory.getLogger(BindingDOMTransactionChainAdapter.class);
public BindingDOMTransactionChainAdapter(final DOMDataBroker chainFactory, final BindingToNormalizedNodeCodec codec,
final TransactionChainListener listener) {
- Preconditions.checkNotNull(chainFactory, "DOM Transaction chain factory must not be null");
+ requireNonNull(chainFactory, "DOM Transaction chain factory must not be null");
this.bindingListener = listener;
this.delegate = chainFactory.createTransactionChain(new DelegateChainListener());
this.codec = codec;
@Override
public ReadTransaction newReadOnlyTransaction() {
- final DOMDataTreeReadTransaction delegateTx = delegate.newReadOnlyTransaction();
- return new BindingDOMReadTransactionAdapter(delegateTx, codec);
+ return new BindingDOMReadTransactionAdapter(createTransaction(delegate::newReadOnlyTransaction), codec);
}
@Override
public WriteTransaction newWriteOnlyTransaction() {
- final DOMDataTreeWriteTransaction delegateTx = delegate.newWriteOnlyTransaction();
+ final DOMDataTreeWriteTransaction delegateTx = createTransaction(delegate::newWriteOnlyTransaction);
return new BindingDOMWriteTransactionAdapter<DOMDataTreeWriteTransaction>(delegateTx, codec) {
@Override
delegate.close();
}
- private final class DelegateChainListener implements TransactionChainListener {
+ private static <T> T createTransaction(final Supplier<T> supplier) {
+ try {
+ return supplier.get();
+ } catch (DOMTransactionChainClosedException e) {
+ throw new TransactionChainClosedException("Transaction chain already closed", e);
+ }
+ }
+ private final class DelegateChainListener implements DOMTransactionChainListener {
@Override
- public void onTransactionChainFailed(final TransactionChain<?, ?> chain,
- final AsyncTransaction<?, ?> transaction, final Throwable cause) {
- Preconditions.checkState(delegate.equals(chain),
- "Illegal state - listener for %s was invoked for incorrect chain %s.", delegate, chain);
+ public void onTransactionChainFailed(final DOMTransactionChain chain,
+ final DOMDataTreeTransaction transaction, final Throwable cause) {
+ checkState(delegate.equals(chain), "Listener for %s was invoked for incorrect chain %s.", delegate, chain);
/*
* Intentionally NOOP, callback for failure, since we are also listening on each transaction
* future for failure, in order to have reference to Binding Transaction (which was seen by client
}
@Override
- public void onTransactionChainSuccessful(final TransactionChain<?, ?> chain) {
- Preconditions.checkState(delegate.equals(chain),
- "Illegal state - listener for %s was invoked for incorrect chain %s.", delegate, chain);
+ public void onTransactionChainSuccessful(final DOMTransactionChain chain) {
+ checkState(delegate.equals(chain), "Listener for %s was invoked for incorrect chain %s.", delegate, chain);
bindingListener.onTransactionChainSuccessful(BindingDOMTransactionChainAdapter.this);
}
}
import com.google.common.collect.ForwardingObject;
import java.util.Collection;
import javax.annotation.Nonnull;
-import org.opendaylight.mdsal.binding.javav2.api.BindingTransactionChain;
import org.opendaylight.mdsal.binding.javav2.api.DataBroker;
import org.opendaylight.mdsal.binding.javav2.api.DataTreeIdentifier;
import org.opendaylight.mdsal.binding.javav2.api.DataTreeListener;
import org.opendaylight.mdsal.binding.javav2.api.DataTreeLoopException;
import org.opendaylight.mdsal.binding.javav2.api.DataTreeProducer;
import org.opendaylight.mdsal.binding.javav2.api.ReadTransaction;
+import org.opendaylight.mdsal.binding.javav2.api.TransactionChain;
+import org.opendaylight.mdsal.binding.javav2.api.TransactionChainListener;
import org.opendaylight.mdsal.binding.javav2.api.WriteTransaction;
import org.opendaylight.mdsal.binding.javav2.spec.base.InstanceIdentifier;
import org.opendaylight.mdsal.binding.javav2.spec.base.TreeNode;
import org.opendaylight.mdsal.common.api.AsyncReadWriteTransaction;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
/**
@Nonnull
@Override
- public <T extends DataTreeListener> ListenerRegistration<T> registerListener(@Nonnull T listener,
- @Nonnull Collection<DataTreeIdentifier<?>> subtrees, boolean allowRxMerges,
- @Nonnull Collection<DataTreeProducer> producers) throws DataTreeLoopException {
+ public <T extends DataTreeListener> ListenerRegistration<T> registerListener(@Nonnull final T listener,
+ @Nonnull final Collection<DataTreeIdentifier<?>> subtrees, final boolean allowRxMerges,
+ @Nonnull final Collection<DataTreeProducer> producers) throws DataTreeLoopException {
return delegate().registerListener(listener, subtrees, allowRxMerges, producers);
}
@Override
- public DataTreeProducer createProducer(Collection<DataTreeIdentifier<?>> subtrees) {
+ public DataTreeProducer createProducer(final Collection<DataTreeIdentifier<?>> subtrees) {
return delegate().createProducer(subtrees);
}
@Override
- public BindingTransactionChain createTransactionChain(TransactionChainListener listener) {
+ public TransactionChain createTransactionChain(final TransactionChainListener listener) {
return delegate().createTransactionChain(listener);
}
* which introduces additional semantics to allocated transactions.
* <ul>
* <li> {@link AsyncDataBroker}
- * <li> {@link TransactionChain}
* </ul>
*
* <p>
* but only to be consumed by them.
*
* @see AsyncDataBroker
- * @see TransactionChain
*
* @param <P> Type of path (subtree identifier), which represents location in tree
* @param <D> Type of data (payload), which represents data payload
* The effects of a successful commit of data depends on listeners and commit participants that are registered with
* the data broker.
*
- * <p>
* <h3>Example usage:</h3>
* <pre>
* private void doWrite(final int tries) {
+++ /dev/null
-/*
- * Copyright (c) 2014 Cisco Systems, Inc. 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.common.api;
-
-import org.opendaylight.yangtools.concepts.Path;
-
-/**
- * A chain of transactions. Transactions in a chain need to be committed in
- * sequence and each transaction should see the effects of previous committed transactions
- * as they occurred. A chain makes no guarantees of atomicity across the chained transactions -
- * the transactions are committed as soon as possible in the order that they were committed.
- * This behaviour is different from the default AsyncDataBroker, where a
- * transaction is always created from the current global state, not taking into
- * account any transactions previously committed by the calling thread. Due to
- * the asynchronous nature of transaction submission this can lead to surprising
- * results. If a thread executes the following sequence sufficiently quickly:
- *
- * <pre><code>
- * AsyncWriteTransaction t1 = broker.newWriteOnlyTransaction();
- * t1.put(id, data);
- * t1.commit();
- *
- * AsyncReadTransaction t2 = broker.newReadOnlyTransaction();
- * Optional<?> maybeData = t2.read(id).get();
- * </code></pre>
- * it may happen, that it sees maybeData.isPresent() == false, simply because
- * t1 has not completed the processes of being applied and t2 is actually
- * allocated from the previous state. This is obviously bad for users who create
- * incremental state in the datastore and actually read what they write in
- * subsequent transactions.
- * Using a TransactionChain instead of a broker solves this particular problem,
- * and leads to expected behavior: t2 will always see the data written in t1
- * present.
- */
-public interface TransactionChain<P extends Path<P>, D> extends AutoCloseable,
- AsyncDataTransactionFactory<P, D> {
-
- /**
- * Create a new read only transaction which will continue the chain.
- *
- * <p>
- * The previous write transaction has to be either SUBMITTED
- * ({@link AsyncWriteTransaction#commit commit} was invoked) or CANCELLED
- * ({@link #close close} was invoked).
- *
- * <p>
- * The returned read-only transaction presents an isolated view of the data if the previous
- * write transaction was successful - in other words, this read-only transaction will see the
- * state changes made by the previous write transaction in the chain. However, state which
- * was introduced by other transactions outside this transaction chain after creation of
- * the previous transaction is not visible.
- *
- * @return New transaction in the chain.
- * @throws IllegalStateException
- * if the previous transaction was not SUBMITTED or CANCELLED.
- * @throws TransactionChainClosedException
- * if the chain has been closed.
- */
- @Override
- AsyncReadTransaction<P, D> newReadOnlyTransaction();
-
- /**
- * Create a new write-only transaction which will continue the chain.
- *
- * <p>
- * The previous write transaction has to be either SUBMITTED
- * ({@link AsyncWriteTransaction#commit commit} was invoked) or CANCELLED
- * ({@link #close close} was invoked)
- *
- * <p>
- * The returned write-only transaction presents an isolated view of the data if the previous
- * write transaction was successful - in other words, this write-only transaction will see the
- * state changes made by the previous write transaction in the chain. However, state which
- * was introduced by other transactions outside this transaction chain after creation of
- * the previous transaction is not visible
- *
- * <p>
- * Committing this write-only transaction using {@link AsyncWriteTransaction#commit commit}
- * will commit the state changes in this transaction to be visible to any subsequent
- * transaction in this chain and also to any transaction outside this chain.
- *
- * @return New transaction in the chain.
- * @throws IllegalStateException
- * if the previous transaction was not SUBMITTED or CANCELLED.
- * @throws TransactionChainClosedException
- * if the chain has been closed.
- */
- @Override
- AsyncWriteTransaction<P, D> newWriteOnlyTransaction();
-
- /**
- * Create a new read-write transaction which will continue the chain.
- *
- * <p>
- * The previous write transaction has to be either SUBMITTED
- * ({@link AsyncWriteTransaction#commit commit} was invoked) or CANCELLED
- * ({@link #close close} was invoked).
- *
- * <p>
- * The returned read-write transaction presents an isolated view of the data if the previous
- * write transaction was successful - in other words, this read-write transaction will see the
- * state changes made by the previous write transaction in the chain. However, state which
- * was introduced by other transactions outside this transaction chain after creation of
- * the previous transaction is not visible.
- *
- * <p>
- * Committing this read-write transaction using {@link AsyncWriteTransaction#commit commit}
- * will commit the state changes in this transaction to be visible to any subsequent
- * transaction in this chain and also to any transaction outside this chain.
- *
- * @return New transaction in the chain.
- * @throws IllegalStateException
- * if the previous transaction was not SUBMITTED or CANCELLED.
- * @throws TransactionChainClosedException
- * if the chain has been closed.
- */
- @Override
- AsyncReadWriteTransaction<P, D> newReadWriteTransaction();
-
- @Override
- void close();
-}
+++ /dev/null
-/*
- * Copyright (c) 2013 Cisco Systems, Inc. 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.common.api;
-
-import org.opendaylight.yangtools.concepts.Path;
-
-/**
- * Interface for creating transaction chains.
- *
- * @deprecated This interface is deprecated and is integrated into AsyncDataBroker replacements.
- */
-@Deprecated
-public interface TransactionChainFactory<P extends Path<P>, D> {
-
- /**
- * Create a new transaction chain. The chain will be initialized to read
- * from its backing datastore, with no outstanding transaction. Listener
- * will be registered to handle chain-level events.
- *
- * @param listener Transaction chain event listener
- * @return A new transaction chain.
- */
- TransactionChain<P, D> createTransactionChain(TransactionChainListener listener);
-}
-
package org.opendaylight.mdsal.dom.api;
import org.opendaylight.mdsal.common.api.AsyncDataBroker;
-import org.opendaylight.mdsal.common.api.TransactionChainFactory;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
*/
public interface DOMDataBroker extends
AsyncDataBroker<YangInstanceIdentifier, NormalizedNode<?, ?>>, DOMTransactionFactory,
- TransactionChainFactory<YangInstanceIdentifier, NormalizedNode<?, ?>>,
DOMExtensibleService<DOMDataBroker, DOMDataBrokerExtension> {
-
- @Override
- DOMTransactionChain createTransactionChain(TransactionChainListener listener);
+ /**
+ * Create a new transaction chain. The chain will be initialized to read from its backing datastore, with
+ * no outstanding transaction. Listener will be registered to handle chain-level events.
+ *
+ * @param listener Transaction chain event listener
+ * @return A new transaction chain.
+ */
+ DOMTransactionChain createTransactionChain(DOMTransactionChainListener listener);
}
*/
package org.opendaylight.mdsal.dom.api;
-import org.opendaylight.mdsal.common.api.TransactionChain;
-import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
-import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
+import org.opendaylight.yangtools.concepts.Registration;
/**
- * A chain of DOM Data transactions.
- * Transactions in a chain need to be committed in sequence and each
- * transaction should see the effects of previous transactions as if they happened. A chain
- * makes no guarantees of atomicity, in fact transactions are committed as soon as possible.
+ * A chain of transactions. Transactions in a chain need to be committed in sequence and each transaction should see
+ * the effects of previous committed transactions as they occurred. A chain makes no guarantees of atomicity across
+ * the chained transactions - the transactions are committed as soon as possible in the order that they were committed.
+ * This behaviour is different from the default AsyncDataBroker, where a transaction is always created from the current
+ * global state, not taking into account any transactions previously committed by the calling thread. Due to
+ * the asynchronous nature of transaction submission this can lead to surprising results. If a thread executes
+ * the following sequence sufficiently quickly:
*
- * <p>
- * This interface is type capture of {@link TransactionChain} for DOM Data Contracts.
+ * <code>
+ * DOMWriteTransaction t1 = broker.newWriteOnlyTransaction();
+ * t1.put(id, data);
+ * t1.commit();
+ *
+ * DOMReadTransaction t2 = broker.newReadOnlyTransaction();
+ * Optional<?> maybeData = t2.read(id).get();
+ * </code>
+ * it may happen, that it sees maybeData.isPresent() == false, simply because t1 has not completed the processes
+ * of being applied and t2 is actually allocated from the previous state. This is obviously bad for users who create
+ * incremental state in the datastore and actually read what they write in subsequent transactions.
+ * Using a TransactionChain instead of a broker solves this particular problem, and leads to expected behavior: t2 will
+ * always see the data written in t1
+ * present.
*/
-public interface DOMTransactionChain extends TransactionChain<YangInstanceIdentifier, NormalizedNode<?, ?>> {
-
+public interface DOMTransactionChain extends Registration, DOMTransactionFactory {
+ /**
+ * Create a new read only transaction which will continue the chain.
+ *
+ * <p>
+ * The previous write transaction has to be either SUBMITTED ({@link DOMDataTreeWriteTransaction#commit commit} was
+ * invoked) or CANCELLED ({@link #close close} was invoked).
+ *
+ * <p>
+ * The returned read-only transaction presents an isolated view of the data if the previous write transaction was
+ * successful - in other words, this read-only transaction will see the state changes made by the previous write
+ * transaction in the chain. However, state which was introduced by other transactions outside this transaction
+ * chain after creation of the previous transaction is not visible.
+ *
+ * @return New transaction in the chain.
+ * @throws IllegalStateException if the previous transaction was not SUBMITTED or CANCELLED.
+ * @throws DOMTransactionChainClosedException if the chain has been closed.
+ */
@Override
DOMDataTreeReadTransaction newReadOnlyTransaction();
+ /**
+ * Create a new write-only transaction which will continue the chain.
+ *
+ * <p>
+ * The previous write transaction has to be either SUBMITTED ({@link DOMDataTreeWriteTransaction#commit commit} was
+ * invoked) or CANCELLED ({@link #close close} was invoked)
+ *
+ * <p>
+ * The returned write-only transaction presents an isolated view of the data if the previous write transaction was
+ * successful - in other words, this write-only transaction will see the state changes made by the previous write
+ * transaction in the chain. However, state which was introduced by other transactions outside this transaction
+ * chain after creation of the previous transaction is not visible
+ *
+ * <p>
+ * Committing this write-only transaction using {@link DOMDataTreeWriteTransaction#commit commit} will commit
+ * the state changes in this transaction to be visible to any subsequent transaction in this chain and also to any
+ * transaction outside this chain.
+ *
+ * @return New transaction in the chain.
+ * @throws IllegalStateException if the previous transaction was not SUBMITTED or CANCELLED.
+ * @throws DOMTransactionChainClosedException if the chain has been closed.
+ */
@Override
DOMDataTreeWriteTransaction newWriteOnlyTransaction();
+ /**
+ * Create a new read-write transaction which will continue the chain.
+ *
+ * <p>
+ * The previous write transaction has to be either SUBMITTED ({@link DOMDataTreeWriteTransaction#commit commit} was
+ * invoked) or CANCELLED ({@link #close close} was invoked).
+ *
+ * <p>
+ * The returned read-write transaction presents an isolated view of the data if the previous write transaction was
+ * successful - in other words, this read-write transaction will see the state changes made by the previous write
+ * transaction in the chain. However, state which was introduced by other transactions outside this transaction
+ * chain after creation of the previous transaction is not visible.
+ *
+ * <p>
+ * Committing this read-write transaction using {@link DOMDataTreeReadWriteTransaction#commit commit} will commit
+ * the state changes in this transaction to be visible to any subsequent transaction in this chain and also to any
+ * transaction outside this chain.
+ *
+ * @return New transaction in the chain.
+ * @throws IllegalStateException if the previous transaction was not SUBMITTED or CANCELLED.
+ * @throws DOMTransactionChainClosedException if the chain has been closed.
+ */
@Override
DOMDataTreeReadWriteTransaction newReadWriteTransaction();
}
--- /dev/null
+/*
+ * Copyright (c) 2018 Pantheon Technologies, 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.api;
+
+/**
+ * Exception thrown when an attempt is made to open a new transaction in a closed chain.
+ */
+public final class DOMTransactionChainClosedException extends IllegalStateException {
+ private static final long serialVersionUID = 1L;
+
+ public DOMTransactionChainClosedException(final String message) {
+ super(message);
+ }
+
+ public DOMTransactionChainClosedException(final String message, final Throwable cause) {
+ super(message, cause);
+ }
+}
--- /dev/null
+/*
+ * Copyright (c) 2018 Pantheon Technologies, 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.api;
+
+import java.util.EventListener;
+
+/**
+ * Listener for transaction chain events.
+ */
+// FIXME: 4.0.0: remove this in favor of a TransactionChain destiny, available as a FluentFuture from
+// DOMTransactionChain
+public interface DOMTransactionChainListener extends EventListener {
+ /**
+ * Invoked if when a transaction in the chain fails. All transactions submitted after the failed transaction, in the
+ * chain, are automatically cancelled by the time this notification is invoked. Open transactions need to be closed
+ * or cancelled.
+ * Implementations should invoke chain.close() to close the chain.
+ *
+ * @param chain Transaction chain which failed
+ * @param transaction Transaction which caused the chain to fail
+ * @param cause The cause of transaction failure
+ */
+ void onTransactionChainFailed(DOMTransactionChain chain, DOMDataTreeTransaction transaction, Throwable cause);
+
+ /**
+ * Invoked when a transaction chain is completed. A transaction chain is considered completed when it has been
+ * closed and all its instructions have completed successfully.
+ *
+ * @param chain Transaction chain which completed
+ */
+ void onTransactionChainSuccessful(DOMTransactionChain chain);
+}
+
import java.util.Map.Entry;
import java.util.concurrent.atomic.AtomicLong;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataBrokerExtension;
import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
import org.opendaylight.mdsal.dom.spi.store.DOMStore;
import org.opendaylight.mdsal.dom.spi.store.DOMStoreTransactionChain;
import org.opendaylight.mdsal.dom.spi.store.DOMStoreTreeChangePublisher;
}
@Override
- public DOMTransactionChain createTransactionChain(final TransactionChainListener listener) {
+ public DOMTransactionChain createTransactionChain(final DOMTransactionChainListener listener) {
checkNotClosed();
final Map<LogicalDatastoreType, DOMStoreTransactionChain> backingChains =
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
import org.opendaylight.mdsal.dom.spi.store.DOMStoreThreePhaseCommitCohort;
import org.opendaylight.mdsal.dom.spi.store.DOMStoreTransactionChain;
import org.slf4j.Logger;
private static final Logger LOG = LoggerFactory.getLogger(DOMDataBrokerTransactionChainImpl.class);
private final AtomicLong txNum = new AtomicLong();
private final AbstractDOMDataBroker broker;
- private final TransactionChainListener listener;
+ private final DOMTransactionChainListener listener;
private final long chainId;
private volatile State state = State.RUNNING;
*/
DOMDataBrokerTransactionChainImpl(final long chainId,
final Map<LogicalDatastoreType, DOMStoreTransactionChain> chains,
- final AbstractDOMDataBroker broker, final TransactionChainListener listener) {
+ final AbstractDOMDataBroker broker, final DOMTransactionChainListener listener) {
super(chains);
this.chainId = chainId;
this.broker = Preconditions.checkNotNull(broker);
import com.google.common.collect.ClassToInstanceMap;
import com.google.common.collect.ImmutableClassToInstanceMap;
import java.util.concurrent.atomic.AtomicLong;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataBrokerExtension;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeService;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
public class ShardedDOMDataBrokerAdapter implements DOMDataBroker {
}
@Override
- public DOMTransactionChain createTransactionChain(final TransactionChainListener listener) {
+ public DOMTransactionChain createTransactionChain(final DOMTransactionChainListener listener) {
return new ShardedDOMTransactionChainAdapter(newChainIdentifier(), service, listener);
}
import java.util.concurrent.atomic.AtomicLong;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
-import org.opendaylight.mdsal.common.api.AsyncTransaction;
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
import org.opendaylight.mdsal.dom.api.DOMDataTreeListener;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeService;
import org.opendaylight.mdsal.dom.api.DOMDataTreeServiceExtension;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
public class ShardedDOMTransactionChainAdapter implements DOMTransactionChain {
private final DOMDataTreeService dataTreeService;
private final Object txChainIdentifier;
private final AtomicLong txNum = new AtomicLong();
- private final TransactionChainListener txChainListener;
+ private final DOMTransactionChainListener txChainListener;
private final CachedDataTreeService cachedDataTreeService;
private TransactionChainWriteTransaction writeTx;
private TransactionChainReadTransaction readTx;
private boolean finished = false;
public ShardedDOMTransactionChainAdapter(final Object txChainIdentifier,
- final DOMDataTreeService dataTreeService,
- final TransactionChainListener txChainListener) {
+ final DOMDataTreeService dataTreeService, final DOMTransactionChainListener txChainListener) {
Preconditions.checkNotNull(dataTreeService);
Preconditions.checkNotNull(txChainIdentifier);
this.dataTreeService = dataTreeService;
Preconditions.checkState(!finished);
}
- public void transactionFailed(final AsyncTransaction<?, ?> tx, final Throwable cause) {
+ public void transactionFailed(final DOMDataTreeTransaction tx, final Throwable cause) {
txChainListener.onTransactionChainFailed(this, tx, cause);
if (writeTx != null) {
writeTx.cancel();
import com.google.common.base.Preconditions;
import javax.annotation.Nonnull;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeListener;
import org.opendaylight.mdsal.dom.api.DOMDataTreeChangeService;
import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
import org.opendaylight.mdsal.dom.spi.ForwardingDOMDataBroker;
import org.opendaylight.yangtools.concepts.ListenerRegistration;
}
@Override
- public PingPongTransactionChain createTransactionChain(final TransactionChainListener listener) {
+ public PingPongTransactionChain createTransactionChain(final DOMTransactionChainListener listener) {
return new PingPongTransactionChain(delegate, listener);
}
import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
import javax.annotation.Nonnull;
import javax.annotation.concurrent.GuardedBy;
-import org.opendaylight.mdsal.common.api.AsyncTransaction;
import org.opendaylight.mdsal.common.api.CommitInfo;
import org.opendaylight.mdsal.common.api.LogicalDatastoreType;
-import org.opendaylight.mdsal.common.api.TransactionChain;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
import org.opendaylight.mdsal.dom.spi.ForwardingDOMDataReadWriteTransaction;
import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
import org.opendaylight.yangtools.yang.data.api.schema.NormalizedNode;
*/
public final class PingPongTransactionChain implements DOMTransactionChain {
private static final Logger LOG = LoggerFactory.getLogger(PingPongTransactionChain.class);
- private final TransactionChainListener listener;
+ private final DOMTransactionChainListener listener;
private final DOMTransactionChain delegate;
@GuardedBy("this")
.newUpdater(PingPongTransactionChain.class, PingPongTransaction.class, "inflightTx");
private volatile PingPongTransaction inflightTx;
- PingPongTransactionChain(final DOMDataBroker broker, final TransactionChainListener listener) {
+ PingPongTransactionChain(final DOMDataBroker broker, final DOMTransactionChainListener listener) {
this.listener = Preconditions.checkNotNull(listener);
- this.delegate = broker.createTransactionChain(new TransactionChainListener() {
+ this.delegate = broker.createTransactionChain(new DOMTransactionChainListener() {
@Override
- public void onTransactionChainFailed(final TransactionChain<?, ?> chain,
- final AsyncTransaction<?, ?> transaction, final Throwable cause) {
+ public void onTransactionChainFailed(final DOMTransactionChain chain,
+ final DOMDataTreeTransaction transaction, final Throwable cause) {
LOG.debug("Transaction chain {} reported failure in {}", chain, transaction, cause);
delegateFailed(chain, cause);
}
@Override
- public void onTransactionChainSuccessful(final TransactionChain<?, ?> chain) {
+ public void onTransactionChainSuccessful(final DOMTransactionChain chain) {
delegateSuccessful(chain);
}
});
}
- void delegateSuccessful(final TransactionChain<?, ?> chain) {
+ void delegateSuccessful(final DOMTransactionChain chain) {
final Entry<PingPongTransaction, Throwable> canceled;
synchronized (this) {
// This looks weird, but we need not hold the lock while invoking callbacks
tx.onFailure(cause);
}
- void delegateFailed(final TransactionChain<?, ?> chain, final Throwable cause) {
+ void delegateFailed(final DOMTransactionChain chain, final Throwable cause) {
final DOMDataTreeReadWriteTransaction frontend;
final PingPongTransaction tx = inflightTx;
package org.opendaylight.mdsal.dom.broker;
import com.google.common.util.concurrent.SettableFuture;
-import org.opendaylight.mdsal.common.api.AsyncTransaction;
-import org.opendaylight.mdsal.common.api.TransactionChain;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
+import org.opendaylight.mdsal.dom.api.DOMDataTreeTransaction;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
/**
* Simple implementation of {@link TransactionChainListener} for testing.
* transaction chain event is retrieved.
*
*/
-class BlockingTransactionChainListener implements TransactionChainListener {
+class BlockingTransactionChainListener implements DOMTransactionChainListener {
private final SettableFuture<Throwable> failFuture = SettableFuture.create();
private final SettableFuture<Void> successFuture = SettableFuture.create();
@Override
- public void onTransactionChainFailed(final TransactionChain<?, ?> chain, final AsyncTransaction<?, ?> transaction,
+ public void onTransactionChainFailed(final DOMTransactionChain chain, final DOMDataTreeTransaction transaction,
final Throwable cause) {
failFuture.set(cause);
}
@Override
- public void onTransactionChainSuccessful(final TransactionChain<?, ?> chain) {
+ public void onTransactionChainSuccessful(final DOMTransactionChain chain) {
successFuture.set(null);
}
import org.junit.Test;
import org.opendaylight.mdsal.common.api.CommitInfo;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeService;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteCursor;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
import org.opendaylight.mdsal.dom.broker.util.TestModel;
import org.opendaylight.yangtools.util.concurrent.FluentFutures;
import org.opendaylight.yangtools.yang.data.impl.schema.ImmutableNodes;
doNothing().when(producer).close();
- TransactionChainListener chainListener = new BlockingTransactionChainListener();
+ DOMTransactionChainListener chainListener = new BlockingTransactionChainListener();
ShardedDOMTransactionChainAdapter transactionChainAdapter =
new ShardedDOMTransactionChainAdapter(identifier, dataTreeService, chainListener);
import com.google.common.collect.ClassToInstanceMap;
import com.google.common.collect.ForwardingObject;
import javax.annotation.Nonnull;
-import org.opendaylight.mdsal.common.api.TransactionChainListener;
import org.opendaylight.mdsal.dom.api.DOMDataBroker;
import org.opendaylight.mdsal.dom.api.DOMDataBrokerExtension;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeReadWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMDataTreeWriteTransaction;
import org.opendaylight.mdsal.dom.api.DOMTransactionChain;
+import org.opendaylight.mdsal.dom.api.DOMTransactionChainListener;
/**
* Utility {@link DOMDataBroker} implementation which forwards all interface
}
@Override
- public DOMTransactionChain createTransactionChain(final TransactionChainListener listener) {
+ public DOMTransactionChain createTransactionChain(final DOMTransactionChainListener listener) {
return delegate().createTransactionChain(listener);
}
* DOM Data Store
*
* <p>
- * DOM Data Store provides transactional tree-like storage for YANG-modeled
- * entities described by YANG schema and represented by {@link NormalizedNode}.
- * Read and write access to stored data is provided only via transactions
- *
- * <p>
- * created using {@link #newReadOnlyTransaction()},
- * {@link #newWriteOnlyTransaction()} and {@link #newReadWriteTransaction()}, or
- * by creating {@link org.opendaylight.mdsal.common.api.TransactionChain}.
+ * DOM Data Store provides transactional tree-like storage for YANG-modeled entities described by YANG schema
+ * and represented by {@link NormalizedNode}. Read and write access to stored data is provided only via transactions
+ * created using {@link #newReadOnlyTransaction()}, {@link #newWriteOnlyTransaction()} and
+ * {@link #newReadWriteTransaction()}, or via {@link DOMStoreTransactionChain}.
*/
public interface DOMStore extends DOMStoreTransactionFactory {
-
/**
* Creates new transaction chain.
*
* @return Newly created transaction chain.
*/
DOMStoreTransactionChain createTransactionChain();
-
}