f80c80001f6bfd33abd95e2d3abaa58770a234d7
[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}
23  * supplier and backend commit coordinator.
24  *
25  * @param <T> transaction identifier type
26  */
27 @Beta
28 public abstract class AbstractSnapshotBackedTransactionChain<T> extends TransactionReadyPrototype<T>
29         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(Object transactionId);
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(Object transactionId) {
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(Object transactionId) {
71             final DataTreeSnapshot ret = snapshot;
72             Preconditions.checkState(ret != null,
73                     "Could not get snapshot for transaction %s - previous transaction %s is not ready yet",
74                     transactionId, 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, "Transaction %s has already been marked as ready",
81                     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(Object transactionId) {
97             throw new IllegalStateException(message);
98         }
99     }
100
101     @SuppressWarnings("rawtypes")
102     private static final AtomicReferenceFieldUpdater<AbstractSnapshotBackedTransactionChain, State> STATE_UPDATER =
103             AtomicReferenceFieldUpdater.newUpdater(AbstractSnapshotBackedTransactionChain.class, State.class, "state");
104     private static final Logger LOG = LoggerFactory.getLogger(AbstractSnapshotBackedTransactionChain.class);
105     private static final Shutdown CLOSED = new Shutdown("Transaction chain is closed");
106     private static final Shutdown FAILED = new Shutdown("Transaction chain has failed");
107     private final Idle idleState;
108     private volatile State state;
109
110     protected AbstractSnapshotBackedTransactionChain() {
111         idleState = new Idle(this);
112         state = idleState;
113     }
114
115     private Entry<State, DataTreeSnapshot> getSnapshot(T transactionId) {
116         final State localState = state;
117         return new SimpleEntry<>(localState, localState.getSnapshot(transactionId));
118     }
119
120     private boolean recordTransaction(final State expected, final DOMStoreWriteTransaction transaction) {
121         final State localState = new Allocated(transaction);
122         return STATE_UPDATER.compareAndSet(this, expected, localState);
123     }
124
125     @Override
126     public final DOMStoreReadTransaction newReadOnlyTransaction() {
127         return newReadOnlyTransaction(nextTransactionIdentifier());
128     }
129
130     protected DOMStoreReadTransaction newReadOnlyTransaction(T transactionId) {
131         final Entry<State, DataTreeSnapshot> entry = getSnapshot(transactionId);
132         return SnapshotBackedTransactions.newReadTransaction(transactionId, getDebugTransactions(), entry.getValue());
133     }
134
135     @Override
136     public final DOMStoreReadWriteTransaction newReadWriteTransaction() {
137         return newReadWriteTransaction(nextTransactionIdentifier());
138     }
139
140     protected DOMStoreReadWriteTransaction newReadWriteTransaction(T transactionId) {
141         Entry<State, DataTreeSnapshot> entry;
142         DOMStoreReadWriteTransaction ret;
143
144         do {
145             entry = getSnapshot(transactionId);
146             ret = new SnapshotBackedReadWriteTransaction<>(transactionId, getDebugTransactions(), entry.getValue(),
147                     this);
148         } while (!recordTransaction(entry.getKey(), ret));
149
150         return ret;
151     }
152
153     @Override
154     public final DOMStoreWriteTransaction newWriteOnlyTransaction() {
155         return newWriteOnlyTransaction(nextTransactionIdentifier());
156     }
157
158     protected DOMStoreWriteTransaction newWriteOnlyTransaction(T transactionId) {
159         Entry<State, DataTreeSnapshot> entry;
160         DOMStoreWriteTransaction ret;
161
162         do {
163             entry = getSnapshot(transactionId);
164             ret = new SnapshotBackedWriteTransaction<>(transactionId, getDebugTransactions(), entry.getValue(), this);
165         } while (!recordTransaction(entry.getKey(), ret));
166
167         return ret;
168     }
169
170     @Override
171     protected final void transactionAborted(final SnapshotBackedWriteTransaction<T> tx) {
172         final State localState = state;
173         if (localState instanceof Allocated) {
174             final Allocated allocated = (Allocated)localState;
175             if (allocated.getTransaction().equals(tx)) {
176                 final boolean success = STATE_UPDATER.compareAndSet(this, localState, idleState);
177                 if (!success) {
178                     LOG.warn(
179                         "Transaction {} aborted, but chain {} state already transitioned from {} to {}, very strange",
180                         tx, this, localState, state);
181                 }
182             }
183         }
184     }
185
186     @Override
187     protected final DOMStoreThreePhaseCommitCohort transactionReady(
188             final SnapshotBackedWriteTransaction<T> tx,
189             final DataTreeModification tree,
190             final Exception readyError) {
191
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), "Mis-ordered ready transaction %s last allocated was %s",
198                     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, readyError);
205     }
206
207     @Override
208     public final void close() {
209         final State localState = state;
210
211         do {
212             Preconditions.checkState(!CLOSED.equals(localState), "Transaction chain %s has been closed", this);
213
214             if (FAILED.equals(localState)) {
215                 LOG.debug("Ignoring user close in failed state");
216                 return;
217             }
218         } while (!STATE_UPDATER.compareAndSet(this, localState, CLOSED));
219     }
220
221     /**
222      * Notify the base logic that a previously-submitted transaction has been committed successfully.
223      *
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 {} to {}, not making it idle",
248                     this, localState, state);
249         }
250     }
251
252     /**
253      * Notify the base logic that a previously-submitted transaction has failed.
254      *
255      * @param transaction Transaction which failed.
256      * @param cause Failure cause
257      */
258     protected final void onTransactionFailed(final SnapshotBackedWriteTransaction<T> transaction,
259             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      *
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      *
281      * @return A new snapshot.
282      */
283     protected abstract DataTreeSnapshot takeSnapshot();
284
285     /**
286      * Create a cohort for driving the transaction through the commit process.
287      *
288      * @param transaction Transaction handle
289      * @param modification {@link DataTreeModification} which needs to be applied to the backend
290      * @param operationError Any previous error that could be reported through three phase commit
291      * @return A {@link DOMStoreThreePhaseCommitCohort} cohort.
292      */
293     protected abstract DOMStoreThreePhaseCommitCohort createCohort(SnapshotBackedWriteTransaction<T> transaction,
294                                                                    DataTreeModification modification,
295                                                                    Exception operationError);
296 }

©2013 OpenDaylight, A Linux Foundation Collaborative Project. All Rights Reserved.
OpenDaylight is a registered trademark of The OpenDaylight Project, Inc.
Linux Foundation and OpenDaylight are registered trademarks of the Linux Foundation.
Linux is a registered trademark of Linus Torvalds.