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