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