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.controller.sal.core.spi.data;
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;
22 * Abstract implementation of the {@link DOMStoreTransactionChain} interface relying on {@link DataTreeSnapshot} supplier
23 * and backend commit coordinator.
25 * @param <T> transaction identifier type
28 public abstract class AbstractSnapshotBackedTransactionChain<T> extends TransactionReadyPrototype<T> implements DOMStoreTransactionChain {
29 private static abstract class State {
31 * Allocate a new snapshot.
33 * @return A new snapshot
35 protected abstract DataTreeSnapshot getSnapshot();
38 private static final class Idle extends State {
39 private final AbstractSnapshotBackedTransactionChain<?> chain;
41 Idle(final AbstractSnapshotBackedTransactionChain<?> chain) {
42 this.chain = Preconditions.checkNotNull(chain);
46 protected DataTreeSnapshot getSnapshot() {
47 return chain.takeSnapshot();
52 * We have a transaction out there.
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;
60 Allocated(final DOMStoreWriteTransaction transaction) {
61 this.transaction = Preconditions.checkNotNull(transaction);
64 public DOMStoreWriteTransaction getTransaction() {
69 protected DataTreeSnapshot getSnapshot() {
70 final DataTreeSnapshot ret = snapshot;
71 Preconditions.checkState(ret != null, "Previous transaction %s is not ready yet", transaction.getIdentifier());
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());
82 * Chain is logically shut down, no further allocation allowed.
84 private static final class Shutdown extends State {
85 private final String message;
87 Shutdown(final String message) {
88 this.message = Preconditions.checkNotNull(message);
92 protected DataTreeSnapshot getSnapshot() {
93 throw new IllegalStateException(message);
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;
106 protected AbstractSnapshotBackedTransactionChain() {
107 idleState = new Idle(this);
111 private Entry<State, DataTreeSnapshot> getSnapshot() {
112 final State localState = state;
113 return new SimpleEntry<>(localState, localState.getSnapshot());
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);
122 public final DOMStoreReadTransaction newReadOnlyTransaction() {
123 return newReadOnlyTransaction(nextTransactionIdentifier());
126 protected DOMStoreReadTransaction newReadOnlyTransaction(T transactionId) {
127 final Entry<State, DataTreeSnapshot> entry = getSnapshot();
128 return SnapshotBackedTransactions.newReadTransaction(transactionId, getDebugTransactions(), entry.getValue());
132 public final DOMStoreReadWriteTransaction newReadWriteTransaction() {
133 return newReadWriteTransaction(nextTransactionIdentifier());
136 protected DOMStoreReadWriteTransaction newReadWriteTransaction(T transactionId) {
137 Entry<State, DataTreeSnapshot> entry;
138 DOMStoreReadWriteTransaction ret;
141 entry = getSnapshot();
142 ret = new SnapshotBackedReadWriteTransaction<T>(transactionId, getDebugTransactions(), entry.getValue(), this);
143 } while (!recordTransaction(entry.getKey(), ret));
149 public final DOMStoreWriteTransaction newWriteOnlyTransaction() {
150 return newWriteOnlyTransaction(nextTransactionIdentifier());
153 protected DOMStoreWriteTransaction newWriteOnlyTransaction(T transactionId) {
154 Entry<State, DataTreeSnapshot> entry;
155 DOMStoreWriteTransaction ret;
158 entry = getSnapshot();
159 ret = new SnapshotBackedWriteTransaction<T>(transactionId, getDebugTransactions(), entry.getValue(), this);
160 } while (!recordTransaction(entry.getKey(), ret));
166 protected final void transactionAborted(final SnapshotBackedWriteTransaction<T> tx) {
167 final State localState = state;
168 if (localState instanceof Allocated) {
169 final Allocated allocated = (Allocated)localState;
170 if (allocated.getTransaction().equals(tx)) {
171 final boolean success = STATE_UPDATER.compareAndSet(this, localState, idleState);
173 LOG.warn("Transaction {} aborted, but chain {} state already transitioned from {} to {}, very strange",
174 tx, this, localState, state);
181 protected final DOMStoreThreePhaseCommitCohort transactionReady(final SnapshotBackedWriteTransaction<T> tx, final DataTreeModification tree) {
182 final State localState = state;
184 if (localState instanceof Allocated) {
185 final Allocated allocated = (Allocated)localState;
186 final DOMStoreWriteTransaction transaction = allocated.getTransaction();
187 Preconditions.checkState(tx.equals(transaction), "Mis-ordered ready transaction %s last allocated was %s", tx, transaction);
188 allocated.setSnapshot(tree);
190 LOG.debug("Ignoring transaction {} readiness due to state {}", tx, localState);
193 return createCohort(tx, tree);
197 public final void close() {
198 final State localState = state;
201 Preconditions.checkState(!CLOSED.equals(localState), "Transaction chain {} has been closed", this);
203 if (FAILED.equals(localState)) {
204 LOG.debug("Ignoring user close in failed state");
207 } while (!STATE_UPDATER.compareAndSet(this, localState, CLOSED));
211 * Notify the base logic that a previously-submitted transaction has been committed successfully.
213 * @param transaction Transaction which completed successfully.
215 protected final void onTransactionCommited(final SnapshotBackedWriteTransaction<T> transaction) {
216 // If the committed transaction was the one we allocated last,
217 // we clear it and the ready snapshot, so the next transaction
218 // allocated refers to the data tree directly.
219 final State localState = state;
221 if (!(localState instanceof Allocated)) {
222 // This can legally happen if the chain is shut down before the transaction was committed
224 LOG.debug("Ignoring successful transaction {} in state {}", transaction, localState);
228 final Allocated allocated = (Allocated)localState;
229 final DOMStoreWriteTransaction tx = allocated.getTransaction();
230 if (!tx.equals(transaction)) {
231 LOG.debug("Ignoring non-latest successful transaction {} in state {}", transaction, allocated);
235 if (!STATE_UPDATER.compareAndSet(this, localState, idleState)) {
236 LOG.debug("Transaction chain {} has already transitioned from {} to {}, not making it idle", this, localState, state);
241 * Notify the base logic that a previously-submitted transaction has failed.
243 * @param transaction Transaction which failed.
244 * @param cause Failure cause
246 protected final void onTransactionFailed(final SnapshotBackedWriteTransaction<T> transaction, final Throwable cause) {
247 LOG.debug("Transaction chain {} failed on transaction {}", this, transaction, cause);
252 * Return the next transaction identifier.
254 * @return transaction identifier.
256 protected abstract T nextTransactionIdentifier();
259 * Inquire as to whether transactions should record their allocation context.
261 * @return True if allocation context should be recorded.
263 protected abstract boolean getDebugTransactions();
266 * Take a fresh {@link DataTreeSnapshot} from the backend.
268 * @return A new snapshot.
270 protected abstract DataTreeSnapshot takeSnapshot();
273 * Create a cohort for driving the transaction through the commit process.
275 * @param transaction Transaction handle
276 * @param modification {@link DataTreeModification} which needs to be applied to the backend
277 * @return A {@link DOMStoreThreePhaseCommitCohort} cohort.
279 protected abstract DOMStoreThreePhaseCommitCohort createCohort(final SnapshotBackedWriteTransaction<T> transaction, final DataTreeModification modification);