61c88d8a331f983043858c52ee96c3a841abbcf6
[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
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 {
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 state = new Allocated(transaction);
124         return STATE_UPDATER.compareAndSet(this, expected, state);
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());
136     }
137
138     @Override
139     public final DOMStoreReadWriteTransaction newReadWriteTransaction() {
140         return newReadWriteTransaction(nextTransactionIdentifier());
141     }
142
143     protected DOMStoreReadWriteTransaction newReadWriteTransaction(T transactionId) {
144         Entry<State, DataTreeSnapshot> entry;
145         DOMStoreReadWriteTransaction ret;
146
147         do {
148             entry = getSnapshot();
149             ret = new SnapshotBackedReadWriteTransaction<T>(transactionId,
150                     getDebugTransactions(), entry.getValue(), this);
151         } while (!recordTransaction(entry.getKey(), ret));
152
153         return ret;
154     }
155
156     @Override
157     public final DOMStoreWriteTransaction newWriteOnlyTransaction() {
158         return newWriteOnlyTransaction(nextTransactionIdentifier());
159     }
160
161     protected DOMStoreWriteTransaction newWriteOnlyTransaction(T transactionId) {
162         Entry<State, DataTreeSnapshot> entry;
163         DOMStoreWriteTransaction ret;
164
165         do {
166             entry = getSnapshot();
167             ret = new SnapshotBackedWriteTransaction<T>(transactionId,
168                     getDebugTransactions(), entry.getValue(), this);
169         } while (!recordTransaction(entry.getKey(), ret));
170
171         return ret;
172     }
173
174     @Override
175     protected final void transactionAborted(final SnapshotBackedWriteTransaction<T> tx) {
176         final State localState = state;
177         if (localState instanceof Allocated) {
178             final Allocated allocated = (Allocated)localState;
179             if (allocated.getTransaction().equals(tx)) {
180                 final boolean success = STATE_UPDATER.compareAndSet(this, localState, idleState);
181                 if (!success) {
182                     LOG.warn("Transaction {} aborted, but chain {} s"
183                             + "tate already transitioned from {} to {}, very strange",
184                         tx, this, localState, state);
185                 }
186             }
187         }
188     }
189
190     @Override
191     protected final DOMStoreThreePhaseCommitCohort transactionReady(
192             final SnapshotBackedWriteTransaction<T> tx, final DataTreeModification tree) {
193         final State localState = state;
194
195         if (localState instanceof Allocated) {
196             final Allocated allocated = (Allocated)localState;
197             final DOMStoreWriteTransaction transaction = allocated.getTransaction();
198             Preconditions.checkState(tx.equals(transaction),
199                     "Mis-ordered ready transaction %s last allocated was %s", tx, transaction);
200             allocated.setSnapshot(tree);
201         } else {
202             LOG.debug("Ignoring transaction {} readiness due to state {}", tx, localState);
203         }
204
205         return createCohort(tx, tree);
206     }
207
208     @Override
209     public final void close() {
210         final State localState = state;
211
212         do {
213             Preconditions.checkState(!CLOSED.equals(localState),
214                     "Transaction chain {} has been closed", this);
215
216             if (FAILED.equals(localState)) {
217                 LOG.debug("Ignoring user close in failed state");
218                 return;
219             }
220         } while (!STATE_UPDATER.compareAndSet(this, localState, CLOSED));
221     }
222
223     /**
224      * Notify the base logic that a previously-submitted transaction has been committed successfully.
225      * @param transaction Transaction which completed successfully.
226      */
227     protected final void onTransactionCommited(final SnapshotBackedWriteTransaction<T> transaction) {
228         // If the committed transaction was the one we allocated last,
229         // we clear it and the ready snapshot, so the next transaction
230         // allocated refers to the data tree directly.
231         final State localState = state;
232
233         if (!(localState instanceof Allocated)) {
234             // This can legally happen if the chain is shut down before the transaction was committed
235             // by the backend.
236             LOG.debug("Ignoring successful transaction {} in state {}", transaction, localState);
237             return;
238         }
239
240         final Allocated allocated = (Allocated)localState;
241         final DOMStoreWriteTransaction tx = allocated.getTransaction();
242         if (!tx.equals(transaction)) {
243             LOG.debug("Ignoring non-latest successful transaction {} in state {}", transaction, allocated);
244             return;
245         }
246
247         if (!STATE_UPDATER.compareAndSet(this, localState, idleState)) {
248             LOG.debug("Transaction chain {} has already transitioned from "
249                     + "{} to {}, not making it idle", this, localState, state);
250         }
251     }
252
253     /**
254      * Notify the base logic that a previously-submitted transaction has failed.
255      * @param transaction Transaction which failed.
256      * @param cause Failure cause
257      */
258     protected final void onTransactionFailed(
259             final SnapshotBackedWriteTransaction<T> transaction, final Throwable cause) {
260         LOG.debug("Transaction chain {} failed on transaction {}", this, transaction, cause);
261         state = FAILED;
262     }
263
264     /**
265      * Return the next transaction identifier.
266      *
267      * @return transaction identifier.
268      */
269     protected abstract T nextTransactionIdentifier();
270
271     /**
272      * Inquire as to whether transactions should record their allocation context.
273      * @return True if allocation context should be recorded.
274      */
275     protected abstract boolean getDebugTransactions();
276
277     /**
278      * Take a fresh {@link DataTreeSnapshot} from the backend.
279      * @return A new snapshot.
280      */
281     protected abstract DataTreeSnapshot takeSnapshot();
282
283     /**
284      * Create a cohort for driving the transaction through the commit process.
285      * @param transaction Transaction handle
286      * @param modification {@link DataTreeModification} which needs to be applied to the backend
287      * @return A {@link DOMStoreThreePhaseCommitCohort} cohort.
288      */
289     protected abstract DOMStoreThreePhaseCommitCohort createCohort(
290             final SnapshotBackedWriteTransaction<T> transaction, final DataTreeModification modification);
291 }