0bd3cf3d7a17264898357bc4951195c4c1100483
[mdsal.git] / dom / mdsal-dom-spi / src / main / java / org / opendaylight / mdsal / dom / spi / store / AbstractSnapshotBackedTransactionChain.java
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.dom.spi.store;
9
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.annotations.Beta;
14 import java.util.AbstractMap.SimpleEntry;
15 import java.util.Map.Entry;
16 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
17 import org.opendaylight.mdsal.dom.spi.store.SnapshotBackedReadTransaction.TransactionClosePrototype;
18 import org.opendaylight.mdsal.dom.spi.store.SnapshotBackedWriteTransaction.TransactionReadyPrototype;
19 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeModification;
20 import org.opendaylight.yangtools.yang.data.tree.api.DataTreeSnapshot;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23
24 /**
25  * Abstract implementation of the {@link DOMStoreTransactionChain} interface relying on {@link DataTreeSnapshot}
26  * supplier and backend commit coordinator.
27  *
28  * @param <T> transaction identifier type
29  */
30 @Beta
31 public abstract class AbstractSnapshotBackedTransactionChain<T>
32         extends TransactionReadyPrototype<T> implements DOMStoreTransactionChain, TransactionClosePrototype<T> {
33     private abstract static sealed class State {
34         /**
35          * Allocate a new snapshot.
36          *
37          * @return A new snapshot
38          */
39         abstract DataTreeSnapshot getSnapshot();
40     }
41
42     private static final class Idle extends State {
43         private final AbstractSnapshotBackedTransactionChain<?> chain;
44
45         Idle(final AbstractSnapshotBackedTransactionChain<?> chain) {
46             this.chain = requireNonNull(chain);
47         }
48
49         @Override
50         DataTreeSnapshot getSnapshot() {
51             return chain.takeSnapshot();
52         }
53     }
54
55     /**
56      * We have a transaction out there.
57      */
58     private static final class Allocated extends State {
59         private static final AtomicReferenceFieldUpdater<Allocated, DataTreeSnapshot> SNAPSHOT_UPDATER =
60                 AtomicReferenceFieldUpdater.newUpdater(Allocated.class, DataTreeSnapshot.class, "snapshot");
61         private final DOMStoreWriteTransaction transaction;
62         private volatile DataTreeSnapshot snapshot;
63
64         Allocated(final DOMStoreWriteTransaction transaction) {
65             this.transaction = requireNonNull(transaction);
66         }
67
68         DOMStoreWriteTransaction getTransaction() {
69             return transaction;
70         }
71
72         @Override
73         DataTreeSnapshot getSnapshot() {
74             final DataTreeSnapshot ret = snapshot;
75             checkState(ret != null, "Previous transaction %s is not ready yet", transaction.getIdentifier());
76             return ret;
77         }
78
79         void setSnapshot(final DataTreeSnapshot snapshot) {
80             final boolean success = SNAPSHOT_UPDATER.compareAndSet(this, null, snapshot);
81             checkState(success, "Transaction %s has already been marked as ready", transaction.getIdentifier());
82         }
83     }
84
85     /**
86      * Chain is logically shut down, no further allocation allowed.
87      */
88     private static final class Shutdown extends State {
89         private final String message;
90
91         Shutdown(final String message) {
92             this.message = requireNonNull(message);
93         }
94
95         @Override
96         DataTreeSnapshot getSnapshot() {
97             throw new IllegalStateException(message);
98         }
99     }
100
101     @SuppressWarnings("rawtypes")
102     private static final AtomicReferenceFieldUpdater<AbstractSnapshotBackedTransactionChain, State>
103         STATE_UPDATER =
104             AtomicReferenceFieldUpdater.newUpdater(AbstractSnapshotBackedTransactionChain.class,
105                     State.class, "state");
106     private static final Logger LOG = LoggerFactory.getLogger(AbstractSnapshotBackedTransactionChain.class);
107     private static final Shutdown CLOSED = new Shutdown("Transaction chain is closed");
108     private static final Shutdown FAILED = new Shutdown("Transaction chain has failed");
109
110     private final Idle idleState;
111     private volatile State state;
112
113     protected AbstractSnapshotBackedTransactionChain() {
114         state = idleState = new Idle(this);
115     }
116
117     private Entry<State, DataTreeSnapshot> getSnapshot() {
118         final State localState = state;
119         return new SimpleEntry<>(localState, localState.getSnapshot());
120     }
121
122     private boolean recordTransaction(final State expected, final DOMStoreWriteTransaction transaction) {
123         final State real = new Allocated(transaction);
124         return STATE_UPDATER.compareAndSet(this, expected, real);
125     }
126
127     @Override
128     public final DOMStoreReadTransaction newReadOnlyTransaction() {
129         return newReadOnlyTransaction(nextTransactionIdentifier());
130     }
131
132     protected DOMStoreReadTransaction newReadOnlyTransaction(final T transactionId) {
133         final Entry<State, DataTreeSnapshot> entry = getSnapshot();
134         return SnapshotBackedTransactions.newReadTransaction(transactionId,
135                 getDebugTransactions(), entry.getValue(), this);
136     }
137
138     @Override
139     public void transactionClosed(final SnapshotBackedReadTransaction<T> tx) {
140         // Defaults to no-op
141     }
142
143     @Override
144     public final DOMStoreReadWriteTransaction newReadWriteTransaction() {
145         return newReadWriteTransaction(nextTransactionIdentifier());
146     }
147
148     protected DOMStoreReadWriteTransaction newReadWriteTransaction(final T transactionId) {
149         Entry<State, DataTreeSnapshot> entry;
150         DOMStoreReadWriteTransaction ret;
151
152         do {
153             entry = getSnapshot();
154             ret = new SnapshotBackedReadWriteTransaction<>(transactionId,
155                     getDebugTransactions(), entry.getValue(), this);
156         } while (!recordTransaction(entry.getKey(), ret));
157
158         return ret;
159     }
160
161     @Override
162     public final DOMStoreWriteTransaction newWriteOnlyTransaction() {
163         return newWriteOnlyTransaction(nextTransactionIdentifier());
164     }
165
166     protected DOMStoreWriteTransaction newWriteOnlyTransaction(final T transactionId) {
167         Entry<State, DataTreeSnapshot> entry;
168         DOMStoreWriteTransaction ret;
169
170         do {
171             entry = getSnapshot();
172             ret = new SnapshotBackedWriteTransaction<>(transactionId, getDebugTransactions(), entry.getValue(), this);
173         } while (!recordTransaction(entry.getKey(), ret));
174
175         return ret;
176     }
177
178     @Override
179     protected final void transactionAborted(final SnapshotBackedWriteTransaction<T> tx) {
180         final State localState = state;
181         if (localState instanceof Allocated allocated) {
182             if (allocated.getTransaction().equals(tx)) {
183                 final boolean success = STATE_UPDATER.compareAndSet(this, localState, idleState);
184                 if (!success) {
185                     LOG.warn("Transaction {} aborted, but chain {} state already transitioned from {} to {}, "
186                         + "very strange", tx, this, localState, state);
187                 }
188             }
189         }
190     }
191
192     @Override
193     protected final DOMStoreThreePhaseCommitCohort transactionReady(final SnapshotBackedWriteTransaction<T> tx,
194             final DataTreeModification tree,
195             final Exception readyError) {
196         final State localState = state;
197
198         if (localState instanceof Allocated allocated) {
199             final DOMStoreWriteTransaction transaction = allocated.getTransaction();
200             checkState(tx.equals(transaction), "Mis-ordered ready transaction %s last allocated was %s", tx,
201                 transaction);
202             allocated.setSnapshot(tree);
203         } else {
204             LOG.debug("Ignoring transaction {} readiness due to state {}", tx, localState);
205         }
206
207         return createCohort(tx, tree, readyError);
208     }
209
210     @Override
211     public final void close() {
212         final State localState = state;
213
214         do {
215             checkState(!CLOSED.equals(localState), "Transaction chain %s has been closed", this);
216
217             if (FAILED.equals(localState)) {
218                 LOG.debug("Ignoring user close in failed state");
219                 return;
220             }
221         } while (!STATE_UPDATER.compareAndSet(this, localState, CLOSED));
222     }
223
224     /**
225      * Notify the base logic that a previously-submitted transaction has been committed successfully.
226      * @param transaction Transaction which completed successfully.
227      */
228     protected final void onTransactionCommited(final SnapshotBackedWriteTransaction<T> transaction) {
229         // If the committed transaction was the one we allocated last,
230         // we clear it and the ready snapshot, so the next transaction
231         // allocated refers to the data tree directly.
232         final State localState = state;
233
234         if (!(localState instanceof Allocated allocated)) {
235             // This can legally happen if the chain is shut down before the transaction was committed
236             // by the backend.
237             LOG.debug("Ignoring successful transaction {} in state {}", transaction, localState);
238             return;
239         }
240
241         final DOMStoreWriteTransaction tx = allocated.getTransaction();
242         if (!tx.equals(transaction)) {
243             LOG.debug("Ignoring non-latest successful transaction {} in state {}", transaction, allocated);
244             return;
245         }
246
247         if (!STATE_UPDATER.compareAndSet(this, localState, idleState)) {
248             LOG.debug("Transaction chain {} has already transitioned from {} to {}, not making it idle", this,
249                 localState, state);
250         }
251     }
252
253     /**
254      * Notify the base logic that a previously-submitted transaction has failed.
255      * @param transaction Transaction which failed.
256      * @param cause Failure cause
257      */
258     protected final void onTransactionFailed(
259             final SnapshotBackedWriteTransaction<T> transaction, final Throwable cause) {
260         LOG.debug("Transaction chain {} failed on transaction {}", this, transaction, cause);
261         state = FAILED;
262     }
263
264     /**
265      * Return the next transaction identifier.
266      *
267      * @return transaction identifier.
268      */
269     protected abstract T nextTransactionIdentifier();
270
271     /**
272      * Inquire as to whether transactions should record their allocation context.
273      * @return True if allocation context should be recorded.
274      */
275     protected abstract boolean getDebugTransactions();
276
277     /**
278      * Take a fresh {@link DataTreeSnapshot} from the backend.
279      * @return A new snapshot.
280      */
281     protected abstract DataTreeSnapshot takeSnapshot();
282
283     /**
284      * Create a cohort for driving the transaction through the commit process.
285      * @param transaction Transaction handle
286      * @param modification {@link DataTreeModification} which needs to be applied to the backend
287      * @param operationError Any previous error that could be reported through three phase commit
288      * @return A {@link DOMStoreThreePhaseCommitCohort} cohort.
289      */
290     protected abstract DOMStoreThreePhaseCommitCohort createCohort(SnapshotBackedWriteTransaction<T> transaction,
291                                                                    DataTreeModification modification,
292                                                                    Exception operationError);
293 }