46018d06d3fca82cada9c2182eb87522dbf79a7e
[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.api.schema.tree.DataTreeModification;
20 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
23
24 /**
25  * Abstract implementation of the {@link DOMStoreTransactionChain}
26  * interface relying on {@link DataTreeSnapshot} supplier
27  * and backend commit coordinator.
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 class State {
34         /**
35          * Allocate a new snapshot.
36          *
37          * @return A new snapshot
38          */
39         protected 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         protected 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         public DOMStoreWriteTransaction getTransaction() {
69             return transaction;
70         }
71
72         @Override
73         protected 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         protected 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     private final Idle idleState;
110     private volatile State state;
111
112     protected AbstractSnapshotBackedTransactionChain() {
113         idleState = new Idle(this);
114         state = idleState;
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,
173                     getDebugTransactions(), entry.getValue(), this);
174         } while (!recordTransaction(entry.getKey(), ret));
175
176         return ret;
177     }
178
179     @Override
180     protected final void transactionAborted(final SnapshotBackedWriteTransaction<T> tx) {
181         final State localState = state;
182         if (localState instanceof Allocated) {
183             final Allocated allocated = (Allocated)localState;
184             if (allocated.getTransaction().equals(tx)) {
185                 final boolean success = STATE_UPDATER.compareAndSet(this, localState, idleState);
186                 if (!success) {
187                     LOG.warn("Transaction {} aborted, but chain {} state already transitioned from {} to {}, "
188                         + "very strange", tx, this, localState, state);
189                 }
190             }
191         }
192     }
193
194     @Override
195     protected final DOMStoreThreePhaseCommitCohort transactionReady(
196             final SnapshotBackedWriteTransaction<T> tx,
197             final DataTreeModification tree,
198             final Exception readyError) {
199         final State localState = state;
200
201         if (localState instanceof Allocated) {
202             final Allocated allocated = (Allocated)localState;
203             final DOMStoreWriteTransaction transaction = allocated.getTransaction();
204             checkState(tx.equals(transaction), "Mis-ordered ready transaction %s last allocated was %s", tx,
205                 transaction);
206             allocated.setSnapshot(tree);
207         } else {
208             LOG.debug("Ignoring transaction {} readiness due to state {}", tx, localState);
209         }
210
211         return createCohort(tx, tree, readyError);
212     }
213
214     @Override
215     public final void close() {
216         final State localState = state;
217
218         do {
219             checkState(!CLOSED.equals(localState), "Transaction chain {} has been closed", this);
220
221             if (FAILED.equals(localState)) {
222                 LOG.debug("Ignoring user close in failed state");
223                 return;
224             }
225         } while (!STATE_UPDATER.compareAndSet(this, localState, CLOSED));
226     }
227
228     /**
229      * Notify the base logic that a previously-submitted transaction has been committed successfully.
230      * @param transaction Transaction which completed successfully.
231      */
232     protected final void onTransactionCommited(final SnapshotBackedWriteTransaction<T> transaction) {
233         // If the committed transaction was the one we allocated last,
234         // we clear it and the ready snapshot, so the next transaction
235         // allocated refers to the data tree directly.
236         final State localState = state;
237
238         if (!(localState instanceof Allocated)) {
239             // This can legally happen if the chain is shut down before the transaction was committed
240             // by the backend.
241             LOG.debug("Ignoring successful transaction {} in state {}", transaction, localState);
242             return;
243         }
244
245         final Allocated allocated = (Allocated)localState;
246         final DOMStoreWriteTransaction tx = allocated.getTransaction();
247         if (!tx.equals(transaction)) {
248             LOG.debug("Ignoring non-latest successful transaction {} in state {}", transaction, allocated);
249             return;
250         }
251
252         if (!STATE_UPDATER.compareAndSet(this, localState, idleState)) {
253             LOG.debug("Transaction chain {} has already transitioned from "
254                     + "{} to {}, not making it idle", this, localState, state);
255         }
256     }
257
258     /**
259      * Notify the base logic that a previously-submitted transaction has failed.
260      * @param transaction Transaction which failed.
261      * @param cause Failure cause
262      */
263     protected final void onTransactionFailed(
264             final SnapshotBackedWriteTransaction<T> transaction, final Throwable cause) {
265         LOG.debug("Transaction chain {} failed on transaction {}", this, transaction, cause);
266         state = FAILED;
267     }
268
269     /**
270      * Return the next transaction identifier.
271      *
272      * @return transaction identifier.
273      */
274     protected abstract T nextTransactionIdentifier();
275
276     /**
277      * Inquire as to whether transactions should record their allocation context.
278      * @return True if allocation context should be recorded.
279      */
280     protected abstract boolean getDebugTransactions();
281
282     /**
283      * Take a fresh {@link DataTreeSnapshot} from the backend.
284      * @return A new snapshot.
285      */
286     protected abstract DataTreeSnapshot takeSnapshot();
287
288     /**
289      * Create a cohort for driving the transaction through the commit process.
290      * @param transaction Transaction handle
291      * @param modification {@link DataTreeModification} which needs to be applied to the backend
292      * @param operationError Any previous error that could be reported through three phase commit
293      * @return A {@link DOMStoreThreePhaseCommitCohort} cohort.
294      */
295     protected abstract DOMStoreThreePhaseCommitCohort createCohort(SnapshotBackedWriteTransaction<T> transaction,
296                                                                    DataTreeModification modification,
297                                                                    Exception operationError);
298 }