Delay snapshot backed transaction ready error
[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,
192             final DataTreeModification tree,
193             final Exception readyError) {
194         final State localState = state;
195
196         if (localState instanceof Allocated) {
197             final Allocated allocated = (Allocated)localState;
198             final DOMStoreWriteTransaction transaction = allocated.getTransaction();
199             Preconditions.checkState(tx.equals(transaction),
200                     "Mis-ordered ready transaction %s last allocated was %s", tx, transaction);
201             allocated.setSnapshot(tree);
202         } else {
203             LOG.debug("Ignoring transaction {} readiness due to state {}", tx, localState);
204         }
205
206         return createCohort(tx, tree, readyError);
207     }
208
209     @Override
210     public final void close() {
211         final State localState = state;
212
213         do {
214             Preconditions.checkState(!CLOSED.equals(localState),
215                     "Transaction chain {} has been closed", this);
216
217             if (FAILED.equals(localState)) {
218                 LOG.debug("Ignoring user close in failed state");
219                 return;
220             }
221         } while (!STATE_UPDATER.compareAndSet(this, localState, CLOSED));
222     }
223
224     /**
225      * Notify the base logic that a previously-submitted transaction has been committed successfully.
226      * @param transaction Transaction which completed successfully.
227      */
228     protected final void onTransactionCommited(final SnapshotBackedWriteTransaction<T> transaction) {
229         // If the committed transaction was the one we allocated last,
230         // we clear it and the ready snapshot, so the next transaction
231         // allocated refers to the data tree directly.
232         final State localState = state;
233
234         if (!(localState instanceof Allocated)) {
235             // This can legally happen if the chain is shut down before the transaction was committed
236             // by the backend.
237             LOG.debug("Ignoring successful transaction {} in state {}", transaction, localState);
238             return;
239         }
240
241         final Allocated allocated = (Allocated)localState;
242         final DOMStoreWriteTransaction tx = allocated.getTransaction();
243         if (!tx.equals(transaction)) {
244             LOG.debug("Ignoring non-latest successful transaction {} in state {}", transaction, allocated);
245             return;
246         }
247
248         if (!STATE_UPDATER.compareAndSet(this, localState, idleState)) {
249             LOG.debug("Transaction chain {} has already transitioned from "
250                     + "{} to {}, not making it idle", this, localState, state);
251         }
252     }
253
254     /**
255      * Notify the base logic that a previously-submitted transaction has failed.
256      * @param transaction Transaction which failed.
257      * @param cause Failure cause
258      */
259     protected final void onTransactionFailed(
260             final SnapshotBackedWriteTransaction<T> transaction, final Throwable cause) {
261         LOG.debug("Transaction chain {} failed on transaction {}", this, transaction, cause);
262         state = FAILED;
263     }
264
265     /**
266      * Return the next transaction identifier.
267      *
268      * @return transaction identifier.
269      */
270     protected abstract T nextTransactionIdentifier();
271
272     /**
273      * Inquire as to whether transactions should record their allocation context.
274      * @return True if allocation context should be recorded.
275      */
276     protected abstract boolean getDebugTransactions();
277
278     /**
279      * Take a fresh {@link DataTreeSnapshot} from the backend.
280      * @return A new snapshot.
281      */
282     protected abstract DataTreeSnapshot takeSnapshot();
283
284     /**
285      * Create a cohort for driving the transaction through the commit process.
286      * @param transaction Transaction handle
287      * @param modification {@link DataTreeModification} which needs to be applied to the backend
288      * @param operationError Any previous error that could be reported through three phase commit
289      * @return A {@link DOMStoreThreePhaseCommitCohort} cohort.
290      */
291     protected abstract DOMStoreThreePhaseCommitCohort createCohort(SnapshotBackedWriteTransaction<T> transaction,
292                                                                    DataTreeModification modification,
293                                                                    Exception operationError);
294 }