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.SnapshotBackedReadTransaction.TransactionClosePrototype;
16 import org.opendaylight.controller.sal.core.spi.data.SnapshotBackedWriteTransaction.TransactionReadyPrototype;
17 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
18 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
19 import org.slf4j.Logger;
20 import org.slf4j.LoggerFactory;
23 * Abstract implementation of the {@link DOMStoreTransactionChain} interface relying on {@link DataTreeSnapshot}
24 * supplier and backend commit coordinator.
26 * @param <T> transaction identifier type
29 public abstract class AbstractSnapshotBackedTransactionChain<T> extends TransactionReadyPrototype<T>
30 implements DOMStoreTransactionChain, TransactionClosePrototype<T> {
31 private abstract static class State {
33 * Allocate a new snapshot.
35 * @return A new snapshot
37 protected abstract DataTreeSnapshot getSnapshot(Object transactionId);
40 private static final class Idle extends State {
41 private final AbstractSnapshotBackedTransactionChain<?> chain;
43 Idle(final AbstractSnapshotBackedTransactionChain<?> chain) {
44 this.chain = Preconditions.checkNotNull(chain);
48 protected DataTreeSnapshot getSnapshot(Object transactionId) {
49 return chain.takeSnapshot();
54 * We have a transaction out there.
56 private static final class Allocated extends State {
57 private static final AtomicReferenceFieldUpdater<Allocated, DataTreeSnapshot> SNAPSHOT_UPDATER =
58 AtomicReferenceFieldUpdater.newUpdater(Allocated.class, DataTreeSnapshot.class, "snapshot");
59 private final DOMStoreWriteTransaction transaction;
60 private volatile DataTreeSnapshot snapshot;
62 Allocated(final DOMStoreWriteTransaction transaction) {
63 this.transaction = Preconditions.checkNotNull(transaction);
66 public DOMStoreWriteTransaction getTransaction() {
71 protected DataTreeSnapshot getSnapshot(Object transactionId) {
72 final DataTreeSnapshot ret = snapshot;
73 Preconditions.checkState(ret != null,
74 "Could not get snapshot for transaction %s - previous transaction %s is not ready yet",
75 transactionId, transaction.getIdentifier());
79 void setSnapshot(final DataTreeSnapshot snapshot) {
80 final boolean success = SNAPSHOT_UPDATER.compareAndSet(this, null, snapshot);
81 Preconditions.checkState(success, "Transaction %s has already been marked as ready",
82 transaction.getIdentifier());
87 * Chain is logically shut down, no further allocation allowed.
89 private static final class Shutdown extends State {
90 private final String message;
92 Shutdown(final String message) {
93 this.message = Preconditions.checkNotNull(message);
97 protected DataTreeSnapshot getSnapshot(Object transactionId) {
98 throw new IllegalStateException(message);
102 @SuppressWarnings("rawtypes")
103 private static final AtomicReferenceFieldUpdater<AbstractSnapshotBackedTransactionChain, State> STATE_UPDATER =
104 AtomicReferenceFieldUpdater.newUpdater(AbstractSnapshotBackedTransactionChain.class, 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(T transactionId) {
117 final State localState = state;
118 return new SimpleEntry<>(localState, localState.getSnapshot(transactionId));
121 private boolean recordTransaction(final State expected, final DOMStoreWriteTransaction transaction) {
122 final State localState = new Allocated(transaction);
123 return STATE_UPDATER.compareAndSet(this, expected, localState);
127 public final DOMStoreReadTransaction newReadOnlyTransaction() {
128 return newReadOnlyTransaction(nextTransactionIdentifier());
131 protected DOMStoreReadTransaction newReadOnlyTransaction(T transactionId) {
132 final Entry<State, DataTreeSnapshot> entry = getSnapshot(transactionId);
133 return SnapshotBackedTransactions.newReadTransaction(transactionId, getDebugTransactions(), entry.getValue(),
138 public void transactionClosed(final SnapshotBackedReadTransaction<T> tx) {
143 public final DOMStoreReadWriteTransaction newReadWriteTransaction() {
144 return newReadWriteTransaction(nextTransactionIdentifier());
147 protected DOMStoreReadWriteTransaction newReadWriteTransaction(T transactionId) {
148 Entry<State, DataTreeSnapshot> entry;
149 DOMStoreReadWriteTransaction ret;
152 entry = getSnapshot(transactionId);
153 ret = new SnapshotBackedReadWriteTransaction<>(transactionId, getDebugTransactions(), entry.getValue(),
155 } while (!recordTransaction(entry.getKey(), ret));
161 public final DOMStoreWriteTransaction newWriteOnlyTransaction() {
162 return newWriteOnlyTransaction(nextTransactionIdentifier());
165 protected DOMStoreWriteTransaction newWriteOnlyTransaction(T transactionId) {
166 Entry<State, DataTreeSnapshot> entry;
167 DOMStoreWriteTransaction ret;
170 entry = getSnapshot(transactionId);
171 ret = new SnapshotBackedWriteTransaction<>(transactionId, getDebugTransactions(), entry.getValue(), this);
172 } while (!recordTransaction(entry.getKey(), ret));
178 protected final void transactionAborted(final SnapshotBackedWriteTransaction<T> tx) {
179 final State localState = state;
180 if (localState instanceof Allocated) {
181 final Allocated allocated = (Allocated)localState;
182 if (allocated.getTransaction().equals(tx)) {
183 final boolean success = STATE_UPDATER.compareAndSet(this, localState, idleState);
186 "Transaction {} aborted, but chain {} state already transitioned from {} to {}, very strange",
187 tx, this, localState, state);
194 protected final DOMStoreThreePhaseCommitCohort transactionReady(
195 final SnapshotBackedWriteTransaction<T> tx,
196 final DataTreeModification tree,
197 final Exception readyError) {
199 final State localState = state;
201 if (localState instanceof Allocated) {
202 final Allocated allocated = (Allocated)localState;
203 final DOMStoreWriteTransaction transaction = allocated.getTransaction();
204 Preconditions.checkState(tx.equals(transaction), "Mis-ordered ready transaction %s last allocated was %s",
206 allocated.setSnapshot(tree);
208 LOG.debug("Ignoring transaction {} readiness due to state {}", tx, localState);
211 return createCohort(tx, tree, readyError);
215 public final void close() {
216 final State localState = state;
219 Preconditions.checkState(!CLOSED.equals(localState), "Transaction chain %s has been closed", this);
221 if (FAILED.equals(localState)) {
222 LOG.debug("Ignoring user close in failed state");
225 } while (!STATE_UPDATER.compareAndSet(this, localState, CLOSED));
229 * Notify the base logic that a previously-submitted transaction has been committed successfully.
231 * @param transaction Transaction which completed successfully.
233 protected final void onTransactionCommited(final SnapshotBackedWriteTransaction<T> transaction) {
234 // If the committed transaction was the one we allocated last,
235 // we clear it and the ready snapshot, so the next transaction
236 // allocated refers to the data tree directly.
237 final State localState = state;
239 if (!(localState instanceof Allocated)) {
240 // This can legally happen if the chain is shut down before the transaction was committed
242 LOG.debug("Ignoring successful transaction {} in state {}", transaction, localState);
246 final Allocated allocated = (Allocated)localState;
247 final DOMStoreWriteTransaction tx = allocated.getTransaction();
248 if (!tx.equals(transaction)) {
249 LOG.debug("Ignoring non-latest successful transaction {} in state {}", transaction, allocated);
253 if (!STATE_UPDATER.compareAndSet(this, localState, idleState)) {
254 LOG.debug("Transaction chain {} has already transitioned from {} to {}, not making it idle",
255 this, localState, state);
260 * Notify the base logic that a previously-submitted transaction has failed.
262 * @param transaction Transaction which failed.
263 * @param cause Failure cause
265 protected final void onTransactionFailed(final SnapshotBackedWriteTransaction<T> transaction,
266 final Throwable cause) {
267 LOG.debug("Transaction chain {} failed on transaction {}", this, transaction, cause);
272 * Return the next transaction identifier.
274 * @return transaction identifier.
276 protected abstract T nextTransactionIdentifier();
279 * Inquire as to whether transactions should record their allocation context.
281 * @return True if allocation context should be recorded.
283 protected abstract boolean getDebugTransactions();
286 * Take a fresh {@link DataTreeSnapshot} from the backend.
288 * @return A new snapshot.
290 protected abstract DataTreeSnapshot takeSnapshot();
293 * Create a cohort for driving the transaction through the commit process.
295 * @param transaction Transaction handle
296 * @param modification {@link DataTreeModification} which needs to be applied to the backend
297 * @param operationError Any previous error that could be reported through three phase commit
298 * @return A {@link DOMStoreThreePhaseCommitCohort} cohort.
300 protected abstract DOMStoreThreePhaseCommitCohort createCohort(SnapshotBackedWriteTransaction<T> transaction,
301 DataTreeModification modification,
302 Exception operationError);