CDS: Implement front-end support for local transactions
[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();
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() {
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() {
70             final DataTreeSnapshot ret = snapshot;
71             Preconditions.checkState(ret != null, "Previous transaction %s is not ready yet", transaction.getIdentifier());
72             return ret;
73         }
74
75         void setSnapshot(final DataTreeSnapshot snapshot) {
76             final boolean success = SNAPSHOT_UPDATER.compareAndSet(this, null, snapshot);
77             Preconditions.checkState(success, "Transaction %s has already been marked as ready", transaction.getIdentifier());
78         }
79     }
80
81     /**
82      * Chain is logically shut down, no further allocation allowed.
83      */
84     private static final class Shutdown extends State {
85         private final String message;
86
87         Shutdown(final String message) {
88             this.message = Preconditions.checkNotNull(message);
89         }
90
91         @Override
92         protected DataTreeSnapshot getSnapshot() {
93             throw new IllegalStateException(message);
94         }
95     }
96
97     @SuppressWarnings("rawtypes")
98     private static final AtomicReferenceFieldUpdater<AbstractSnapshotBackedTransactionChain, State> STATE_UPDATER =
99             AtomicReferenceFieldUpdater.newUpdater(AbstractSnapshotBackedTransactionChain.class, State.class, "state");
100     private static final Logger LOG = LoggerFactory.getLogger(AbstractSnapshotBackedTransactionChain.class);
101     private static final Shutdown CLOSED = new Shutdown("Transaction chain is closed");
102     private static final Shutdown FAILED = new Shutdown("Transaction chain has failed");
103     private final Idle idleState;
104     private volatile State state;
105
106     protected AbstractSnapshotBackedTransactionChain() {
107         idleState = new Idle(this);
108         state = idleState;
109     }
110
111     private Entry<State, DataTreeSnapshot> getSnapshot() {
112         final State localState = state;
113         return new SimpleEntry<>(localState, localState.getSnapshot());
114     }
115
116     private boolean recordTransaction(final State expected, final DOMStoreWriteTransaction transaction) {
117         final State state = new Allocated(transaction);
118         return STATE_UPDATER.compareAndSet(this, expected, state);
119     }
120
121     @Override
122     public final DOMStoreReadTransaction newReadOnlyTransaction() {
123         return newReadOnlyTransaction(nextTransactionIdentifier());
124     }
125
126     protected DOMStoreReadTransaction newReadOnlyTransaction(T transactionId) {
127         final Entry<State, DataTreeSnapshot> entry = getSnapshot();
128         return SnapshotBackedTransactions.newReadTransaction(transactionId, getDebugTransactions(), entry.getValue());
129     }
130
131     @Override
132     public final DOMStoreReadWriteTransaction newReadWriteTransaction() {
133         return newReadWriteTransaction(nextTransactionIdentifier());
134     }
135
136     protected DOMStoreReadWriteTransaction newReadWriteTransaction(T transactionId) {
137         Entry<State, DataTreeSnapshot> entry;
138         DOMStoreReadWriteTransaction ret;
139
140         do {
141             entry = getSnapshot();
142             ret = new SnapshotBackedReadWriteTransaction<T>(transactionId, getDebugTransactions(), entry.getValue(), this);
143         } while (!recordTransaction(entry.getKey(), ret));
144
145         return ret;
146     }
147
148     @Override
149     public final DOMStoreWriteTransaction newWriteOnlyTransaction() {
150         return newWriteOnlyTransaction(nextTransactionIdentifier());
151     }
152
153     protected DOMStoreWriteTransaction newWriteOnlyTransaction(T transactionId) {
154         Entry<State, DataTreeSnapshot> entry;
155         DOMStoreWriteTransaction ret;
156
157         do {
158             entry = getSnapshot();
159             ret = new SnapshotBackedWriteTransaction<T>(transactionId, getDebugTransactions(), entry.getValue(), this);
160         } while (!recordTransaction(entry.getKey(), ret));
161
162         return ret;
163     }
164
165     @Override
166     protected final void transactionAborted(final SnapshotBackedWriteTransaction<T> tx) {
167         final State localState = state;
168         if (localState instanceof Allocated) {
169             final Allocated allocated = (Allocated)localState;
170             if (allocated.getTransaction().equals(tx)) {
171                 final boolean success = STATE_UPDATER.compareAndSet(this, localState, idleState);
172                 if (!success) {
173                     LOG.warn("Transaction {} aborted, but chain {} state already transitioned from {} to {}, very strange",
174                         tx, this, localState, state);
175                 }
176             }
177         }
178     }
179
180     @Override
181     protected final DOMStoreThreePhaseCommitCohort transactionReady(final SnapshotBackedWriteTransaction<T> tx, final DataTreeModification tree) {
182         final State localState = state;
183
184         if (localState instanceof Allocated) {
185             final Allocated allocated = (Allocated)localState;
186             final DOMStoreWriteTransaction transaction = allocated.getTransaction();
187             Preconditions.checkState(tx.equals(transaction), "Mis-ordered ready transaction %s last allocated was %s", tx, transaction);
188             allocated.setSnapshot(tree);
189         } else {
190             LOG.debug("Ignoring transaction {} readiness due to state {}", tx, localState);
191         }
192
193         return createCohort(tx, tree);
194     }
195
196     @Override
197     public final void close() {
198         final State localState = state;
199
200         do {
201             Preconditions.checkState(!CLOSED.equals(localState), "Transaction chain {} has been closed", this);
202
203             if (FAILED.equals(localState)) {
204                 LOG.debug("Ignoring user close in failed state");
205                 return;
206             }
207         } while (!STATE_UPDATER.compareAndSet(this, localState, CLOSED));
208     }
209
210     /**
211      * Notify the base logic that a previously-submitted transaction has been committed successfully.
212      *
213      * @param transaction Transaction which completed successfully.
214      */
215     protected final void onTransactionCommited(final SnapshotBackedWriteTransaction<T> transaction) {
216         // If the committed transaction was the one we allocated last,
217         // we clear it and the ready snapshot, so the next transaction
218         // allocated refers to the data tree directly.
219         final State localState = state;
220
221         if (!(localState instanceof Allocated)) {
222             // This can legally happen if the chain is shut down before the transaction was committed
223             // by the backend.
224             LOG.debug("Ignoring successful transaction {} in state {}", transaction, localState);
225             return;
226         }
227
228         final Allocated allocated = (Allocated)localState;
229         final DOMStoreWriteTransaction tx = allocated.getTransaction();
230         if (!tx.equals(transaction)) {
231             LOG.debug("Ignoring non-latest successful transaction {} in state {}", transaction, allocated);
232             return;
233         }
234
235         if (!STATE_UPDATER.compareAndSet(this, localState, idleState)) {
236             LOG.debug("Transaction chain {} has already transitioned from {} to {}, not making it idle", this, localState, state);
237         }
238     }
239
240     /**
241      * Notify the base logic that a previously-submitted transaction has failed.
242      *
243      * @param transaction Transaction which failed.
244      * @param cause Failure cause
245      */
246     protected final void onTransactionFailed(final SnapshotBackedWriteTransaction<T> transaction, final Throwable cause) {
247         LOG.debug("Transaction chain {} failed on transaction {}", this, transaction, cause);
248         state = FAILED;
249     }
250
251     /**
252      * Return the next transaction identifier.
253      *
254      * @return transaction identifier.
255      */
256     protected abstract T nextTransactionIdentifier();
257
258     /**
259      * Inquire as to whether transactions should record their allocation context.
260      *
261      * @return True if allocation context should be recorded.
262      */
263     protected abstract boolean getDebugTransactions();
264
265     /**
266      * Take a fresh {@link DataTreeSnapshot} from the backend.
267      *
268      * @return A new snapshot.
269      */
270     protected abstract DataTreeSnapshot takeSnapshot();
271
272     /**
273      * Create a cohort for driving the transaction through the commit process.
274      *
275      * @param transaction Transaction handle
276      * @param modification {@link DataTreeModification} which needs to be applied to the backend
277      * @return A {@link DOMStoreThreePhaseCommitCohort} cohort.
278      */
279     protected abstract DOMStoreThreePhaseCommitCohort createCohort(final SnapshotBackedWriteTransaction<T> transaction, final DataTreeModification modification);
280 }