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 static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
13 import com.google.common.annotations.Beta;
14 import java.util.AbstractMap.SimpleEntry;
15 import java.util.Map.Entry;
16 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
17 import org.opendaylight.mdsal.dom.spi.store.SnapshotBackedReadTransaction.TransactionClosePrototype;
18 import org.opendaylight.mdsal.dom.spi.store.SnapshotBackedWriteTransaction.TransactionReadyPrototype;
19 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
20 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
21 import org.slf4j.Logger;
22 import org.slf4j.LoggerFactory;
25 * Abstract implementation of the {@link DOMStoreTransactionChain}
26 * interface relying on {@link DataTreeSnapshot} supplier
27 * and backend commit coordinator.
28 * @param <T> transaction identifier type
31 public abstract class AbstractSnapshotBackedTransactionChain<T>
32 extends TransactionReadyPrototype<T> implements DOMStoreTransactionChain, TransactionClosePrototype<T> {
33 private abstract static class State {
35 * Allocate a new snapshot.
37 * @return A new snapshot
39 protected abstract DataTreeSnapshot getSnapshot();
42 private static final class Idle extends State {
43 private final AbstractSnapshotBackedTransactionChain<?> chain;
45 Idle(final AbstractSnapshotBackedTransactionChain<?> chain) {
46 this.chain = requireNonNull(chain);
50 protected DataTreeSnapshot getSnapshot() {
51 return chain.takeSnapshot();
56 * We have a transaction out there.
58 private static final class Allocated extends State {
59 private static final AtomicReferenceFieldUpdater<Allocated, DataTreeSnapshot> SNAPSHOT_UPDATER =
60 AtomicReferenceFieldUpdater.newUpdater(Allocated.class, DataTreeSnapshot.class, "snapshot");
61 private final DOMStoreWriteTransaction transaction;
62 private volatile DataTreeSnapshot snapshot;
64 Allocated(final DOMStoreWriteTransaction transaction) {
65 this.transaction = requireNonNull(transaction);
68 public DOMStoreWriteTransaction getTransaction() {
73 protected DataTreeSnapshot getSnapshot() {
74 final DataTreeSnapshot ret = snapshot;
75 checkState(ret != null, "Previous transaction %s is not ready yet", transaction.getIdentifier());
79 void setSnapshot(final DataTreeSnapshot snapshot) {
80 final boolean success = SNAPSHOT_UPDATER.compareAndSet(this, null, snapshot);
81 checkState(success, "Transaction %s has already been marked as ready", transaction.getIdentifier());
86 * Chain is logically shut down, no further allocation allowed.
88 private static final class Shutdown extends State {
89 private final String message;
91 Shutdown(final String message) {
92 this.message = requireNonNull(message);
96 protected DataTreeSnapshot getSnapshot() {
97 throw new IllegalStateException(message);
101 @SuppressWarnings("rawtypes")
102 private static final AtomicReferenceFieldUpdater<AbstractSnapshotBackedTransactionChain, State>
104 AtomicReferenceFieldUpdater.newUpdater(AbstractSnapshotBackedTransactionChain.class,
105 State.class, "state");
106 private static final Logger LOG = LoggerFactory.getLogger(AbstractSnapshotBackedTransactionChain.class);
107 private static final Shutdown CLOSED = new Shutdown("Transaction chain is closed");
108 private static final Shutdown FAILED = new Shutdown("Transaction chain has failed");
109 private final Idle idleState;
110 private volatile State state;
112 protected AbstractSnapshotBackedTransactionChain() {
113 idleState = new Idle(this);
117 private Entry<State, DataTreeSnapshot> getSnapshot() {
118 final State localState = state;
119 return new SimpleEntry<>(localState, localState.getSnapshot());
122 private boolean recordTransaction(final State expected, final DOMStoreWriteTransaction transaction) {
123 final State real = new Allocated(transaction);
124 return STATE_UPDATER.compareAndSet(this, expected, real);
128 public final DOMStoreReadTransaction newReadOnlyTransaction() {
129 return newReadOnlyTransaction(nextTransactionIdentifier());
132 protected DOMStoreReadTransaction newReadOnlyTransaction(final T transactionId) {
133 final Entry<State, DataTreeSnapshot> entry = getSnapshot();
134 return SnapshotBackedTransactions.newReadTransaction(transactionId,
135 getDebugTransactions(), entry.getValue(), this);
139 public void transactionClosed(final SnapshotBackedReadTransaction<T> tx) {
144 public final DOMStoreReadWriteTransaction newReadWriteTransaction() {
145 return newReadWriteTransaction(nextTransactionIdentifier());
148 protected DOMStoreReadWriteTransaction newReadWriteTransaction(final T transactionId) {
149 Entry<State, DataTreeSnapshot> entry;
150 DOMStoreReadWriteTransaction ret;
153 entry = getSnapshot();
154 ret = new SnapshotBackedReadWriteTransaction<>(transactionId,
155 getDebugTransactions(), entry.getValue(), this);
156 } while (!recordTransaction(entry.getKey(), ret));
162 public final DOMStoreWriteTransaction newWriteOnlyTransaction() {
163 return newWriteOnlyTransaction(nextTransactionIdentifier());
166 protected DOMStoreWriteTransaction newWriteOnlyTransaction(final T transactionId) {
167 Entry<State, DataTreeSnapshot> entry;
168 DOMStoreWriteTransaction ret;
171 entry = getSnapshot();
172 ret = new SnapshotBackedWriteTransaction<>(transactionId,
173 getDebugTransactions(), entry.getValue(), this);
174 } while (!recordTransaction(entry.getKey(), ret));
180 protected final void transactionAborted(final SnapshotBackedWriteTransaction<T> tx) {
181 final State localState = state;
182 if (localState instanceof Allocated) {
183 final Allocated allocated = (Allocated)localState;
184 if (allocated.getTransaction().equals(tx)) {
185 final boolean success = STATE_UPDATER.compareAndSet(this, localState, idleState);
187 LOG.warn("Transaction {} aborted, but chain {} state already transitioned from {} to {}, "
188 + "very strange", tx, this, localState, state);
195 protected final DOMStoreThreePhaseCommitCohort transactionReady(
196 final SnapshotBackedWriteTransaction<T> tx,
197 final DataTreeModification tree,
198 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 checkState(tx.equals(transaction), "Mis-ordered ready transaction %s last allocated was %s", tx,
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 checkState(!CLOSED.equals(localState), "Transaction chain {} 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.
230 * @param transaction Transaction which completed successfully.
232 protected final void onTransactionCommited(final SnapshotBackedWriteTransaction<T> transaction) {
233 // If the committed transaction was the one we allocated last,
234 // we clear it and the ready snapshot, so the next transaction
235 // allocated refers to the data tree directly.
236 final State localState = state;
238 if (!(localState instanceof Allocated)) {
239 // This can legally happen if the chain is shut down before the transaction was committed
241 LOG.debug("Ignoring successful transaction {} in state {}", transaction, localState);
245 final Allocated allocated = (Allocated)localState;
246 final DOMStoreWriteTransaction tx = allocated.getTransaction();
247 if (!tx.equals(transaction)) {
248 LOG.debug("Ignoring non-latest successful transaction {} in state {}", transaction, allocated);
252 if (!STATE_UPDATER.compareAndSet(this, localState, idleState)) {
253 LOG.debug("Transaction chain {} has already transitioned from "
254 + "{} to {}, not making it idle", this, localState, state);
259 * Notify the base logic that a previously-submitted transaction has failed.
260 * @param transaction Transaction which failed.
261 * @param cause Failure cause
263 protected final void onTransactionFailed(
264 final SnapshotBackedWriteTransaction<T> transaction, final Throwable cause) {
265 LOG.debug("Transaction chain {} failed on transaction {}", this, transaction, cause);
270 * Return the next transaction identifier.
272 * @return transaction identifier.
274 protected abstract T nextTransactionIdentifier();
277 * Inquire as to whether transactions should record their allocation context.
278 * @return True if allocation context should be recorded.
280 protected abstract boolean getDebugTransactions();
283 * Take a fresh {@link DataTreeSnapshot} from the backend.
284 * @return A new snapshot.
286 protected abstract DataTreeSnapshot takeSnapshot();
289 * Create a cohort for driving the transaction through the commit process.
290 * @param transaction Transaction handle
291 * @param modification {@link DataTreeModification} which needs to be applied to the backend
292 * @param operationError Any previous error that could be reported through three phase commit
293 * @return A {@link DOMStoreThreePhaseCommitCohort} cohort.
295 protected abstract DOMStoreThreePhaseCommitCohort createCohort(SnapshotBackedWriteTransaction<T> transaction,
296 DataTreeModification modification,
297 Exception operationError);