Allow SnapshotBackedReadTransaction customization
[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 com.google.common.annotations.Beta;
11 import com.google.common.base.Preconditions;
12 import java.util.AbstractMap.SimpleEntry;
13 import java.util.Map.Entry;
14 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
15 import org.opendaylight.mdsal.dom.spi.store.SnapshotBackedReadTransaction.TransactionClosePrototype;
16 import org.opendaylight.mdsal.dom.spi.store.SnapshotBackedWriteTransaction.TransactionReadyPrototype;
17 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
18 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
21
22 /**
23  * Abstract implementation of the {@link DOMStoreTransactionChain}
24  * interface relying on {@link DataTreeSnapshot} supplier
25  * and backend commit coordinator.
26  * @param <T> transaction identifier type
27  */
28 @Beta
29 public abstract class AbstractSnapshotBackedTransactionChain<T>
30         extends TransactionReadyPrototype<T> implements DOMStoreTransactionChain, TransactionClosePrototype<T> {
31     private abstract static class State {
32         /**
33          * Allocate a new snapshot.
34          *
35          * @return A new snapshot
36          */
37         protected abstract DataTreeSnapshot getSnapshot();
38     }
39
40     private static final class Idle extends State {
41         private final AbstractSnapshotBackedTransactionChain<?> chain;
42
43         Idle(final AbstractSnapshotBackedTransactionChain<?> chain) {
44             this.chain = Preconditions.checkNotNull(chain);
45         }
46
47         @Override
48         protected DataTreeSnapshot getSnapshot() {
49             return chain.takeSnapshot();
50         }
51     }
52
53     /**
54      * We have a transaction out there.
55      */
56     private static final class Allocated extends State {
57         private static final AtomicReferenceFieldUpdater<Allocated, DataTreeSnapshot> SNAPSHOT_UPDATER =
58                 AtomicReferenceFieldUpdater.newUpdater(Allocated.class, DataTreeSnapshot.class, "snapshot");
59         private final DOMStoreWriteTransaction transaction;
60         private volatile DataTreeSnapshot snapshot;
61
62         Allocated(final DOMStoreWriteTransaction transaction) {
63             this.transaction = Preconditions.checkNotNull(transaction);
64         }
65
66         public DOMStoreWriteTransaction getTransaction() {
67             return transaction;
68         }
69
70         @Override
71         protected DataTreeSnapshot getSnapshot() {
72             final DataTreeSnapshot ret = snapshot;
73             Preconditions.checkState(ret != null,
74                     "Previous transaction %s is not ready yet", transaction.getIdentifier());
75             return ret;
76         }
77
78         void setSnapshot(final DataTreeSnapshot snapshot) {
79             final boolean success = SNAPSHOT_UPDATER.compareAndSet(this, null, snapshot);
80             Preconditions.checkState(success,
81                     "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 = Preconditions.checkNotNull(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(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(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(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             Preconditions.checkState(tx.equals(transaction),
205                     "Mis-ordered ready transaction %s last allocated was %s", tx, 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             Preconditions.checkState(!CLOSED.equals(localState),
220                     "Transaction chain {} has been closed", this);
221
222             if (FAILED.equals(localState)) {
223                 LOG.debug("Ignoring user close in failed state");
224                 return;
225             }
226         } while (!STATE_UPDATER.compareAndSet(this, localState, CLOSED));
227     }
228
229     /**
230      * Notify the base logic that a previously-submitted transaction has been committed successfully.
231      * @param transaction Transaction which completed successfully.
232      */
233     protected final void onTransactionCommited(final SnapshotBackedWriteTransaction<T> transaction) {
234         // If the committed transaction was the one we allocated last,
235         // we clear it and the ready snapshot, so the next transaction
236         // allocated refers to the data tree directly.
237         final State localState = state;
238
239         if (!(localState instanceof Allocated)) {
240             // This can legally happen if the chain is shut down before the transaction was committed
241             // by the backend.
242             LOG.debug("Ignoring successful transaction {} in state {}", transaction, localState);
243             return;
244         }
245
246         final Allocated allocated = (Allocated)localState;
247         final DOMStoreWriteTransaction tx = allocated.getTransaction();
248         if (!tx.equals(transaction)) {
249             LOG.debug("Ignoring non-latest successful transaction {} in state {}", transaction, allocated);
250             return;
251         }
252
253         if (!STATE_UPDATER.compareAndSet(this, localState, idleState)) {
254             LOG.debug("Transaction chain {} has already transitioned from "
255                     + "{} to {}, not making it idle", this, localState, state);
256         }
257     }
258
259     /**
260      * Notify the base logic that a previously-submitted transaction has failed.
261      * @param transaction Transaction which failed.
262      * @param cause Failure cause
263      */
264     protected final void onTransactionFailed(
265             final SnapshotBackedWriteTransaction<T> transaction, final Throwable cause) {
266         LOG.debug("Transaction chain {} failed on transaction {}", this, transaction, cause);
267         state = FAILED;
268     }
269
270     /**
271      * Return the next transaction identifier.
272      *
273      * @return transaction identifier.
274      */
275     protected abstract T nextTransactionIdentifier();
276
277     /**
278      * Inquire as to whether transactions should record their allocation context.
279      * @return True if allocation context should be recorded.
280      */
281     protected abstract boolean getDebugTransactions();
282
283     /**
284      * Take a fresh {@link DataTreeSnapshot} from the backend.
285      * @return A new snapshot.
286      */
287     protected abstract DataTreeSnapshot takeSnapshot();
288
289     /**
290      * Create a cohort for driving the transaction through the commit process.
291      * @param transaction Transaction handle
292      * @param modification {@link DataTreeModification} which needs to be applied to the backend
293      * @param operationError Any previous error that could be reported through three phase commit
294      * @return A {@link DOMStoreThreePhaseCommitCohort} cohort.
295      */
296     protected abstract DOMStoreThreePhaseCommitCohort createCohort(SnapshotBackedWriteTransaction<T> transaction,
297                                                                    DataTreeModification modification,
298                                                                    Exception operationError);
299 }