2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.dom.spi.store;
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;
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
28 public abstract class AbstractSnapshotBackedTransactionChain<T>
29 extends TransactionReadyPrototype<T> implements DOMStoreTransactionChain {
30 private abstract static class State {
32 * Allocate a new snapshot.
34 * @return A new snapshot
36 protected abstract DataTreeSnapshot getSnapshot();
39 private static final class Idle extends State {
40 private final AbstractSnapshotBackedTransactionChain<?> chain;
42 Idle(final AbstractSnapshotBackedTransactionChain<?> chain) {
43 this.chain = Preconditions.checkNotNull(chain);
47 protected DataTreeSnapshot getSnapshot() {
48 return chain.takeSnapshot();
53 * We have a transaction out there.
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;
61 Allocated(final DOMStoreWriteTransaction transaction) {
62 this.transaction = Preconditions.checkNotNull(transaction);
65 public DOMStoreWriteTransaction getTransaction() {
70 protected DataTreeSnapshot getSnapshot() {
71 final DataTreeSnapshot ret = snapshot;
72 Preconditions.checkState(ret != null,
73 "Previous transaction %s is not ready yet", transaction.getIdentifier());
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());
85 * Chain is logically shut down, no further allocation allowed.
87 private static final class Shutdown extends State {
88 private final String message;
90 Shutdown(final String message) {
91 this.message = Preconditions.checkNotNull(message);
95 protected DataTreeSnapshot getSnapshot() {
96 throw new IllegalStateException(message);
100 @SuppressWarnings("rawtypes")
101 private static final AtomicReferenceFieldUpdater<AbstractSnapshotBackedTransactionChain, State>
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;
111 protected AbstractSnapshotBackedTransactionChain() {
112 idleState = new Idle(this);
116 private Entry<State, DataTreeSnapshot> getSnapshot() {
117 final State localState = state;
118 return new SimpleEntry<>(localState, localState.getSnapshot());
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);
127 public final DOMStoreReadTransaction newReadOnlyTransaction() {
128 return newReadOnlyTransaction(nextTransactionIdentifier());
131 protected DOMStoreReadTransaction newReadOnlyTransaction(T transactionId) {
132 final Entry<State, DataTreeSnapshot> entry = getSnapshot();
133 return SnapshotBackedTransactions.newReadTransaction(transactionId,
134 getDebugTransactions(), entry.getValue());
138 public final DOMStoreReadWriteTransaction newReadWriteTransaction() {
139 return newReadWriteTransaction(nextTransactionIdentifier());
142 protected DOMStoreReadWriteTransaction newReadWriteTransaction(T transactionId) {
143 Entry<State, DataTreeSnapshot> entry;
144 DOMStoreReadWriteTransaction ret;
147 entry = getSnapshot();
148 ret = new SnapshotBackedReadWriteTransaction<>(transactionId,
149 getDebugTransactions(), entry.getValue(), this);
150 } while (!recordTransaction(entry.getKey(), ret));
156 public final DOMStoreWriteTransaction newWriteOnlyTransaction() {
157 return newWriteOnlyTransaction(nextTransactionIdentifier());
160 protected DOMStoreWriteTransaction newWriteOnlyTransaction(T transactionId) {
161 Entry<State, DataTreeSnapshot> entry;
162 DOMStoreWriteTransaction ret;
165 entry = getSnapshot();
166 ret = new SnapshotBackedWriteTransaction<>(transactionId,
167 getDebugTransactions(), entry.getValue(), this);
168 } while (!recordTransaction(entry.getKey(), ret));
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);
181 LOG.warn("Transaction {} aborted, but chain {} s"
182 + "tate already transitioned from {} to {}, very strange",
183 tx, this, localState, state);
190 protected final DOMStoreThreePhaseCommitCohort transactionReady(
191 final SnapshotBackedWriteTransaction<T> tx,
192 final DataTreeModification tree,
193 final Exception readyError) {
194 final State localState = state;
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);
203 LOG.debug("Ignoring transaction {} readiness due to state {}", tx, localState);
206 return createCohort(tx, tree, readyError);
210 public final void close() {
211 final State localState = state;
214 Preconditions.checkState(!CLOSED.equals(localState),
215 "Transaction chain {} has been closed", this);
217 if (FAILED.equals(localState)) {
218 LOG.debug("Ignoring user close in failed state");
221 } while (!STATE_UPDATER.compareAndSet(this, localState, CLOSED));
225 * Notify the base logic that a previously-submitted transaction has been committed successfully.
226 * @param transaction Transaction which completed successfully.
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;
234 if (!(localState instanceof Allocated)) {
235 // This can legally happen if the chain is shut down before the transaction was committed
237 LOG.debug("Ignoring successful transaction {} in state {}", transaction, localState);
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);
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);
255 * Notify the base logic that a previously-submitted transaction has failed.
256 * @param transaction Transaction which failed.
257 * @param cause Failure cause
259 protected final void onTransactionFailed(
260 final SnapshotBackedWriteTransaction<T> transaction, final Throwable cause) {
261 LOG.debug("Transaction chain {} failed on transaction {}", this, transaction, cause);
266 * Return the next transaction identifier.
268 * @return transaction identifier.
270 protected abstract T nextTransactionIdentifier();
273 * Inquire as to whether transactions should record their allocation context.
274 * @return True if allocation context should be recorded.
276 protected abstract boolean getDebugTransactions();
279 * Take a fresh {@link DataTreeSnapshot} from the backend.
280 * @return A new snapshot.
282 protected abstract DataTreeSnapshot takeSnapshot();
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.
291 protected abstract DOMStoreThreePhaseCommitCohort createCohort(SnapshotBackedWriteTransaction<T> transaction,
292 DataTreeModification modification,
293 Exception operationError);