Interface DOMDataTreeWriteTransaction

  • All Superinterfaces:
    DOMDataTreeTransaction, DOMDataTreeWriteOperations, org.opendaylight.yangtools.concepts.Identifiable<Object>
    All Known Subinterfaces:
    DOMDataTreeReadWriteTransaction
    All Known Implementing Classes:
    ForwardingDOMDataReadWriteTransaction, ForwardingDOMDataWriteTransaction, ShardedDOMWriteTransactionAdapter, TransactionChainWriteTransaction

    public interface DOMDataTreeWriteTransaction
    extends DOMDataTreeTransaction, DOMDataTreeWriteOperations
    Write transaction provides mutation capabilities for a data tree.

    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.

    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.

    Applications make changes to the local data tree in the transaction by via the put, merge, and delete operations.

    Put operation

    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.

    Performing the following put operations:

     1) container { list [ a ] }
     2) container { list [ b ] }
     
    will result in the following data being present:
     container { list [ b ] }
     

    Merge operation

    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.

    Performing the following merge operations:

     1) container { list [ a ] }
     2) container { list [ b ] }
     
    will result in the following data being present:
     container { list [ a, b ] }
     
    This also means that storing the container will preserve any augmentations which have been attached to it.

    Delete operation

    Removes a piece of data from a specified path.

    After applying changes to the local data tree, applications publish the changes proposed in the transaction by calling 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.

    The transaction commit may fail due to a concurrent transaction modifying and committing data in an incompatible way. See commit() for more concrete commit failure examples.

    Implementation Note: This interface is not intended to be implemented by users of MD-SAL, but only to be consumed by them.

    • Method Detail

      • commit

        @CheckReturnValue
        @NonNull FluentFuture<? extends @NonNull CommitInfo> commit()
        Commits this transaction to be asynchronously applied to update the logical data tree. The returned FluentFuture conveys the result of applying the data changes.

        This call logically seals the transaction, which prevents the client from further changing the data tree using this transaction. Any subsequent calls to put(LogicalDatastoreType, Path, Object), merge(LogicalDatastoreType, Path, Object), delete(LogicalDatastoreType, Path) will fail with IllegalStateException. The transaction is marked as committed and enqueued into the data store back-end for processing.

        Whether or not the commit is successful is determined by versioning of the data tree and validation of registered commit participants if the transaction changes the data tree.

        The effects of a successful commit of data depends on listeners and commit participants that are registered with the data broker.

        Example usage:

          private void doWrite(final int tries) {
              WriteTransaction writeTx = dataBroker.newWriteOnlyTransaction();
              MyDataObject data = ...;
              InstanceIdentifier<MyDataObject> path = ...;
              writeTx.put(LogicalDatastoreType.OPERATIONAL, path, data);
              Futures.addCallback(writeTx.commit(), new FutureCallback<CommitInfo>() {
                  public void onSuccess(CommitInfo result) {
                      // succeeded
                  }
                  public void onFailure(Throwable t) {
                      if (t instanceof OptimisticLockFailedException) {
                          if(( tries - 1) > 0 ) {
                              // do retry
                              doWrite(tries - 1);
                          } else {
                              // out of retries
                          }
                      } else {
                          // failed due to another type of TransactionCommitFailedException.
                      }
                  });
         }
         ...
         doWrite(2);
         

        Failure scenarios

        Transaction may fail because of multiple reasons, such as

        • Another transaction finished earlier and modified the same node in a non-compatible way (see below). In this case the returned future will fail with an OptimisticLockFailedException. It is the responsibility of the caller to create a new transaction and commit the same modification again in order to update data tree. Warning: In most cases, retrying after an OptimisticLockFailedException will result in a high probability of success. However, there are scenarios, albeit unusual, where any number of retries will not succeed. Therefore it is strongly recommended to limit the number of retries (2 or 3) to avoid an endless loop.
        • Data change introduced by this transaction did not pass validation by commit handlers or data was incorrectly structured. Returned future will fail with a DataValidationFailedException. User should not retry to create new transaction with same data, since it probably will fail again.

        Change compatibility

        There are several sets of changes which could be considered incompatible between two transactions which are derived from same initial state. Rules for conflict detection applies recursively for each subtree level.

        Change compatibility of leafs, leaf-list items

        Following table shows state changes and failures between two concurrent transactions, which are based on same initial state, Tx 1 completes successfully before Tx 2 is committed.
        Change compatibility of leaf values
        Initial state Tx 1 Tx 2 Result
        Empty put(A,1) put(A,2) Tx 2 will fail, state is A=1
        Empty put(A,1) merge(A,2) A=2
        Empty merge(A,1) put(A,2) Tx 2 will fail, state is A=1
        Empty merge(A,1) merge(A,2) A=2
        A=0 put(A,1) put(A,2) Tx 2 will fail, A=1
        A=0 put(A,1) merge(A,2) A=2
        A=0 merge(A,1) put(A,2) Tx 2 will fail, A=1
        A=0 merge(A,1) merge(A,2) A=2
        A=0 delete(A) put(A,2) Tx 2 will fail, A does not exists
        A=0 delete(A) merge(A,2) A=2

        Change compatibility of subtrees

        Following table shows state changes and failures between two concurrent transactions, which are based on same initial state, Tx 1 completes successfully before Tx 2 is committed.
        Change compatibility of containers
        Initial state Tx 1 Tx 2 Result
        Empty put(TOP,[]) put(TOP,[]) Tx 2 will fail, state is TOP=[]
        Empty put(TOP,[]) merge(TOP,[]) TOP=[]
        Empty put(TOP,[FOO=1]) put(TOP,[BAR=1]) Tx 2 will fail, state is TOP=[FOO=1]
        Empty put(TOP,[FOO=1]) merge(TOP,[BAR=1]) TOP=[FOO=1,BAR=1]
        Empty merge(TOP,[FOO=1]) put(TOP,[BAR=1]) Tx 2 will fail, state is TOP=[FOO=1]
        Empty merge(TOP,[FOO=1]) merge(TOP,[BAR=1]) TOP=[FOO=1,BAR=1]
        TOP=[] put(TOP,[FOO=1]) put(TOP,[BAR=1]) Tx 2 will fail, state is TOP=[FOO=1]
        TOP=[] put(TOP,[FOO=1]) merge(TOP,[BAR=1]) state is TOP=[FOO=1,BAR=1]
        TOP=[] merge(TOP,[FOO=1]) put(TOP,[BAR=1]) Tx 2 will fail, state is TOP=[FOO=1]
        TOP=[] merge(TOP,[FOO=1]) merge(TOP,[BAR=1]) state is TOP=[FOO=1,BAR=1]
        TOP=[] delete(TOP) put(TOP,[BAR=1]) Tx 2 will fail, state is empty store
        TOP=[] delete(TOP) merge(TOP,[BAR=1]) state is TOP=[BAR=1]
        TOP=[] put(TOP/FOO,1) put(TOP/BAR,1]) state is TOP=[FOO=1,BAR=1]
        TOP=[] put(TOP/FOO,1) merge(TOP/BAR,1) state is TOP=[FOO=1,BAR=1]
        TOP=[] merge(TOP/FOO,1) put(TOP/BAR,1) state is TOP=[FOO=1,BAR=1]
        TOP=[] merge(TOP/FOO,1) merge(TOP/BAR,1) state is TOP=[FOO=1,BAR=1]
        TOP=[] delete(TOP) put(TOP/BAR,1) Tx 2 will fail, state is empty store
        TOP=[] delete(TOP) merge(TOP/BAR,1] Tx 2 will fail, state is empty store
        TOP=[FOO=1] put(TOP/FOO,2) put(TOP/BAR,1) state is TOP=[FOO=2,BAR=1]
        TOP=[FOO=1] put(TOP/FOO,2) merge(TOP/BAR,1) state is TOP=[FOO=2,BAR=1]
        TOP=[FOO=1] merge(TOP/FOO,2) put(TOP/BAR,1) state is TOP=[FOO=2,BAR=1]
        TOP=[FOO=1] merge(TOP/FOO,2) merge(TOP/BAR,1) state is TOP=[FOO=2,BAR=1]
        TOP=[FOO=1] delete(TOP/FOO) put(TOP/BAR,1) state is TOP=[BAR=1]
        TOP=[FOO=1] delete(TOP/FOO) merge(TOP/BAR,1] state is TOP=[BAR=1]

        Examples of failure scenarios

        Conflict of two transactions

        This example illustrates two concurrent transactions, which derived from same initial state of data tree and proposes conflicting modifications.
         txA = broker.newWriteTransaction(); // allocates new transaction, data tree is empty
         txB = broker.newWriteTransaction(); // allocates new transaction, data tree is empty
         txA.put(CONFIGURATION, PATH, A);    // writes to PATH value A
         txB.put(CONFIGURATION, PATH, B)     // writes to PATH value B
         ListenableFuture futureA = txA.commit(); // transaction A is sealed and committed
         ListenebleFuture futureB = txB.commit(); // transaction B is sealed and committed
         
        Commit of transaction A will be processed asynchronously and data tree will be updated to contain value A for PATH. Returned FluentFuture will successfully complete once state is applied to data tree. Commit of Transaction B will fail, because previous transaction also modified path in a concurrent way. The state introduced by transaction B will not be applied. Returned FluentFuture object will fail with OptimisticLockFailedException exception, which indicates to client that concurrent transaction prevented the committed transaction from being applied.

        A successful commit produces implementation-specific CommitInfo structure, which is used to communicate post-condition information to the caller. Such information can contain commit-id, timing information or any other information the implementation wishes to share.

        Returns:
        a FluentFuture containing the result of the commit information. The Future blocks until the commit operation is complete. A successful commit returns nothing. On failure, the Future will fail with a TransactionCommitFailedException or an exception derived from TransactionCommitFailedException.
        Throws:
        IllegalStateException - if the transaction is already committed or was canceled.
      • cancel

        boolean cancel()
        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 finished transaction (future returned by commit() already successfully completed) will always fail (return false).
        Returns:
        false if the task could not be cancelled, typically because it has already completed normally; true otherwise