2 * Copyright (c) 2015 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.broker;
10 import static java.util.Objects.requireNonNull;
12 import com.google.common.base.Preconditions;
13 import com.google.common.base.Verify;
14 import com.google.common.collect.ImmutableSet;
15 import java.util.Collection;
18 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
19 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
20 import org.checkerframework.checker.lock.qual.GuardedBy;
21 import org.checkerframework.checker.lock.qual.Holding;
22 import org.opendaylight.mdsal.common.api.CommitInfo;
23 import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
24 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
25 import org.opendaylight.mdsal.dom.api.DOMDataTreeListener;
26 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
27 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerBusyException;
28 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerException;
29 import org.opendaylight.mdsal.dom.api.DOMDataTreeShard;
30 import org.slf4j.Logger;
31 import org.slf4j.LoggerFactory;
33 class ShardedDOMDataTreeProducer implements DOMDataTreeProducer {
34 private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTreeProducer.class);
36 private final Set<DOMDataTreeIdentifier> subtrees;
37 private final ShardedDOMDataTree dataTree;
39 private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
40 CURRENT_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
41 ShardedDOMDataTreeWriteTransaction.class, "currentTx");
42 private volatile ShardedDOMDataTreeWriteTransaction currentTx;
44 private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
45 OPEN_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
46 ShardedDOMDataTreeWriteTransaction.class, "openTx");
47 private volatile ShardedDOMDataTreeWriteTransaction openTx;
49 private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
50 LAST_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
51 ShardedDOMDataTreeWriteTransaction.class, "lastTx");
52 private volatile ShardedDOMDataTreeWriteTransaction lastTx;
54 private static final AtomicIntegerFieldUpdater<ShardedDOMDataTreeProducer> CLOSED_UPDATER =
55 AtomicIntegerFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class, "closed");
56 private volatile int closed;
58 private volatile DOMDataTreeListener attachedListener;
59 private volatile ProducerLayout layout;
61 ShardedDOMDataTreeProducer(final ShardedDOMDataTree dataTree,
62 final Collection<DOMDataTreeIdentifier> subtrees,
63 final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
64 this.dataTree = requireNonNull(dataTree);
65 this.subtrees = ImmutableSet.copyOf(subtrees);
66 this.layout = ProducerLayout.create(shardMap);
69 static DOMDataTreeProducer create(final ShardedDOMDataTree dataTree,
70 final Collection<DOMDataTreeIdentifier> subtrees,
71 final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
72 return new ShardedDOMDataTreeProducer(dataTree, subtrees, shardMap);
75 private void checkNotClosed() {
76 Preconditions.checkState(closed == 0, "Producer is already closed");
79 private void checkIdle() {
80 Preconditions.checkState(openTx == null, "Transaction %s is still open", openTx);
83 void subshardAdded(final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
85 layout = layout.reshard(shardMap);
89 public DOMDataTreeCursorAwareTransaction createTransaction(final boolean isolated) {
93 LOG.debug("Creating transaction from producer {}", this);
95 final ShardedDOMDataTreeWriteTransaction current = CURRENT_UPDATER.getAndSet(this, null);
96 final ShardedDOMDataTreeWriteTransaction ret;
98 ret = createIsolatedTransaction(layout, current);
100 ret = createReusedTransaction(layout, current);
103 final boolean success = OPEN_UPDATER.compareAndSet(this, null, ret);
104 Preconditions.checkState(success, "Illegal concurrent access to producer %s detected", this);
108 // This may look weird, but this has side-effects on local's producers, hence it needs to be properly synchronized
109 // so that it happens-after submitTransaction() which may have been stolen by a callback.
111 private ShardedDOMDataTreeWriteTransaction createTransaction(final ProducerLayout local) {
112 return new ShardedDOMDataTreeWriteTransaction(this, local.createTransactions(), local);
116 // Isolated case. If we have a previous transaction, submit it before returning this one.
117 private synchronized ShardedDOMDataTreeWriteTransaction createIsolatedTransaction(
118 final ProducerLayout local, final ShardedDOMDataTreeWriteTransaction current) {
119 if (current != null) {
120 submitTransaction(current);
123 return createTransaction(local);
126 private ShardedDOMDataTreeWriteTransaction createReusedTransaction(final ProducerLayout local,
127 final ShardedDOMDataTreeWriteTransaction current) {
128 if (current != null) {
129 // Lock-free fast path
130 if (local.equals(current.getLayout())) {
131 LOG.debug("Reusing previous transaction {} since there is still a transaction inflight",
132 current.getIdentifier());
136 synchronized (this) {
137 submitTransaction(current);
138 return createTransaction(local);
142 // Null indicates we have not seen a previous transaction -- which does not mean it is ready, as it may have
143 // been stolen and in is process of being submitted.
144 synchronized (this) {
145 return createTransaction(local);
150 private void submitTransaction(final ShardedDOMDataTreeWriteTransaction tx) {
152 tx.doSubmit(this::transactionSuccessful, this::transactionFailed);
156 @SuppressWarnings("checkstyle:hiddenField")
157 public DOMDataTreeProducer createProducer(final Collection<DOMDataTreeIdentifier> subtrees) {
161 final ProducerLayout local = layout;
163 for (final DOMDataTreeIdentifier s : subtrees) {
164 // Check if the subtree was visible at any time
165 Preconditions.checkArgument(local.haveSubtree(s), "Subtree %s was never available in producer %s", s, this);
166 // Check if the subtree has not been delegated to a child
167 final DOMDataTreeProducer child = local.lookupChild(s);
168 Preconditions.checkArgument(child == null, "Subtree %s is delegated to child producer %s", s, child);
170 // Check if part of the requested subtree is not delegated to a child.
171 for (final DOMDataTreeIdentifier c : local.getChildTrees()) {
172 Preconditions.checkArgument(!s.contains(c),
173 "Subtree %s cannot be delegated as it is a superset of already-delegated %s", s, c);
178 final DOMDataTreeProducer ret;
179 synchronized (this) {
180 ret = dataTree.createProducer(this, subtrees);
183 layout = local.addChild(ret, subtrees);
187 boolean isDelegatedToChild(final DOMDataTreeIdentifier path) {
188 return layout.lookupChild(path) != null;
192 public void close() throws DOMDataTreeProducerException {
193 if (openTx != null) {
194 throw new DOMDataTreeProducerBusyException(String.format("Transaction %s is still open", openTx));
197 if (CLOSED_UPDATER.compareAndSet(this, 0, 1)) {
198 synchronized (this) {
199 dataTree.destroyProducer(this);
205 protected Set<DOMDataTreeIdentifier> getSubtrees() {
209 void cancelTransaction(final ShardedDOMDataTreeWriteTransaction transaction) {
210 final boolean success = OPEN_UPDATER.compareAndSet(this, transaction, null);
212 LOG.debug("Transaction {} cancelled", transaction);
214 LOG.warn("Transaction {} is not open in producer {}", transaction, this);
218 // Called when the user submits a transaction
219 void transactionSubmitted(final ShardedDOMDataTreeWriteTransaction transaction) {
220 final boolean wasOpen = OPEN_UPDATER.compareAndSet(this, transaction, null);
221 Preconditions.checkState(wasOpen, "Attempted to submit non-open transaction %s", transaction);
223 if (lastTx == null) {
224 // No transaction outstanding, we need to submit it now
225 synchronized (this) {
226 submitTransaction(transaction);
232 // There is a potentially-racing submitted transaction. Publish the new one, which may end up being
233 // picked up by processNextTransaction.
234 final boolean success = CURRENT_UPDATER.compareAndSet(this, null, transaction);
235 Verify.verify(success);
237 // Now a quick check: if the racing transaction completed in between, it may have missed the current
238 // transaction, hence we need to re-check
239 if (lastTx == null) {
240 submitCurrentTransaction();
244 private void submitCurrentTransaction() {
245 final ShardedDOMDataTreeWriteTransaction current = currentTx;
246 if (current != null) {
247 synchronized (this) {
248 if (CURRENT_UPDATER.compareAndSet(this, current, null)) {
249 submitTransaction(current);
255 private void transactionSuccessful(final ShardedDOMDataTreeWriteTransaction tx) {
256 LOG.debug("Transaction {} completed successfully", tx.getIdentifier());
258 tx.onTransactionSuccess(CommitInfo.empty());
259 transactionCompleted(tx);
262 private void transactionFailed(final ShardedDOMDataTreeWriteTransaction tx, final Throwable throwable) {
263 LOG.debug("Transaction {} failed", tx.getIdentifier(), throwable);
265 tx.onTransactionFailure(throwable);
266 // FIXME: transaction failure should result in a hard error
267 transactionCompleted(tx);
270 private void transactionCompleted(final ShardedDOMDataTreeWriteTransaction tx) {
271 final boolean wasLast = LAST_UPDATER.compareAndSet(this, tx, null);
273 submitCurrentTransaction();
277 void bindToListener(final DOMDataTreeListener listener) {
278 final DOMDataTreeListener local = attachedListener;
279 Preconditions.checkState(local == null, "Producer %s is already attached to listener %s", this, local);
280 this.attachedListener = requireNonNull(listener);