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