Allow SnapshotBackedReadTransaction customization
[controller.git] / opendaylight / md-sal / sal-dom-spi / src / main / java / org / opendaylight / controller / sal / core / spi / data / 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.controller.sal.core.spi.data;
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.controller.sal.core.spi.data.SnapshotBackedReadTransaction.TransactionClosePrototype;
16 import org.opendaylight.controller.sal.core.spi.data.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} interface relying on {@link DataTreeSnapshot}
24  * supplier and backend commit coordinator.
25  *
26  * @param <T> transaction identifier type
27  */
28 @Beta
29 public abstract class AbstractSnapshotBackedTransactionChain<T> extends TransactionReadyPrototype<T>
30         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(Object transactionId);
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(Object transactionId) {
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(Object transactionId) {
72             final DataTreeSnapshot ret = snapshot;
73             Preconditions.checkState(ret != null,
74                     "Could not get snapshot for transaction %s - previous transaction %s is not ready yet",
75                     transactionId, transaction.getIdentifier());
76             return ret;
77         }
78
79         void setSnapshot(final DataTreeSnapshot snapshot) {
80             final boolean success = SNAPSHOT_UPDATER.compareAndSet(this, null, snapshot);
81             Preconditions.checkState(success, "Transaction %s has already been marked as ready",
82                     transaction.getIdentifier());
83         }
84     }
85
86     /**
87      * Chain is logically shut down, no further allocation allowed.
88      */
89     private static final class Shutdown extends State {
90         private final String message;
91
92         Shutdown(final String message) {
93             this.message = Preconditions.checkNotNull(message);
94         }
95
96         @Override
97         protected DataTreeSnapshot getSnapshot(Object transactionId) {
98             throw new IllegalStateException(message);
99         }
100     }
101
102     @SuppressWarnings("rawtypes")
103     private static final AtomicReferenceFieldUpdater<AbstractSnapshotBackedTransactionChain, State> STATE_UPDATER =
104             AtomicReferenceFieldUpdater.newUpdater(AbstractSnapshotBackedTransactionChain.class, State.class, "state");
105     private static final Logger LOG = LoggerFactory.getLogger(AbstractSnapshotBackedTransactionChain.class);
106     private static final Shutdown CLOSED = new Shutdown("Transaction chain is closed");
107     private static final Shutdown FAILED = new Shutdown("Transaction chain has failed");
108     private final Idle idleState;
109     private volatile State state;
110
111     protected AbstractSnapshotBackedTransactionChain() {
112         idleState = new Idle(this);
113         state = idleState;
114     }
115
116     private Entry<State, DataTreeSnapshot> getSnapshot(T transactionId) {
117         final State localState = state;
118         return new SimpleEntry<>(localState, localState.getSnapshot(transactionId));
119     }
120
121     private boolean recordTransaction(final State expected, final DOMStoreWriteTransaction transaction) {
122         final State localState = new Allocated(transaction);
123         return STATE_UPDATER.compareAndSet(this, expected, localState);
124     }
125
126     @Override
127     public final DOMStoreReadTransaction newReadOnlyTransaction() {
128         return newReadOnlyTransaction(nextTransactionIdentifier());
129     }
130
131     protected DOMStoreReadTransaction newReadOnlyTransaction(T transactionId) {
132         final Entry<State, DataTreeSnapshot> entry = getSnapshot(transactionId);
133         return SnapshotBackedTransactions.newReadTransaction(transactionId, getDebugTransactions(), entry.getValue(),
134             this);
135     }
136
137     @Override
138     public void transactionClosed(final SnapshotBackedReadTransaction<T> tx) {
139         // Defaults to no-op
140     }
141
142     @Override
143     public final DOMStoreReadWriteTransaction newReadWriteTransaction() {
144         return newReadWriteTransaction(nextTransactionIdentifier());
145     }
146
147     protected DOMStoreReadWriteTransaction newReadWriteTransaction(T transactionId) {
148         Entry<State, DataTreeSnapshot> entry;
149         DOMStoreReadWriteTransaction ret;
150
151         do {
152             entry = getSnapshot(transactionId);
153             ret = new SnapshotBackedReadWriteTransaction<>(transactionId, getDebugTransactions(), entry.getValue(),
154                     this);
155         } while (!recordTransaction(entry.getKey(), ret));
156
157         return ret;
158     }
159
160     @Override
161     public final DOMStoreWriteTransaction newWriteOnlyTransaction() {
162         return newWriteOnlyTransaction(nextTransactionIdentifier());
163     }
164
165     protected DOMStoreWriteTransaction newWriteOnlyTransaction(T transactionId) {
166         Entry<State, DataTreeSnapshot> entry;
167         DOMStoreWriteTransaction ret;
168
169         do {
170             entry = getSnapshot(transactionId);
171             ret = new SnapshotBackedWriteTransaction<>(transactionId, getDebugTransactions(), entry.getValue(), this);
172         } while (!recordTransaction(entry.getKey(), ret));
173
174         return ret;
175     }
176
177     @Override
178     protected final void transactionAborted(final SnapshotBackedWriteTransaction<T> tx) {
179         final State localState = state;
180         if (localState instanceof Allocated) {
181             final Allocated allocated = (Allocated)localState;
182             if (allocated.getTransaction().equals(tx)) {
183                 final boolean success = STATE_UPDATER.compareAndSet(this, localState, idleState);
184                 if (!success) {
185                     LOG.warn(
186                         "Transaction {} aborted, but chain {} state already transitioned from {} to {}, very strange",
187                         tx, this, localState, state);
188                 }
189             }
190         }
191     }
192
193     @Override
194     protected final DOMStoreThreePhaseCommitCohort transactionReady(
195             final SnapshotBackedWriteTransaction<T> tx,
196             final DataTreeModification tree,
197             final Exception readyError) {
198
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), "Mis-ordered ready transaction %s last allocated was %s",
205                     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), "Transaction chain %s 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      *
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 {} to {}, not making it idle",
255                     this, localState, state);
256         }
257     }
258
259     /**
260      * Notify the base logic that a previously-submitted transaction has failed.
261      *
262      * @param transaction Transaction which failed.
263      * @param cause Failure cause
264      */
265     protected final void onTransactionFailed(final SnapshotBackedWriteTransaction<T> transaction,
266             final Throwable cause) {
267         LOG.debug("Transaction chain {} failed on transaction {}", this, transaction, cause);
268         state = FAILED;
269     }
270
271     /**
272      * Return the next transaction identifier.
273      *
274      * @return transaction identifier.
275      */
276     protected abstract T nextTransactionIdentifier();
277
278     /**
279      * Inquire as to whether transactions should record their allocation context.
280      *
281      * @return True if allocation context should be recorded.
282      */
283     protected abstract boolean getDebugTransactions();
284
285     /**
286      * Take a fresh {@link DataTreeSnapshot} from the backend.
287      *
288      * @return A new snapshot.
289      */
290     protected abstract DataTreeSnapshot takeSnapshot();
291
292     /**
293      * Create a cohort for driving the transaction through the commit process.
294      *
295      * @param transaction Transaction handle
296      * @param modification {@link DataTreeModification} which needs to be applied to the backend
297      * @param operationError Any previous error that could be reported through three phase commit
298      * @return A {@link DOMStoreThreePhaseCommitCohort} cohort.
299      */
300     protected abstract DOMStoreThreePhaseCommitCohort createCohort(SnapshotBackedWriteTransaction<T> transaction,
301                                                                    DataTreeModification modification,
302                                                                    Exception operationError);
303 }