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 com.google.common.base.Preconditions;
11 import com.google.common.base.Verify;
12 import com.google.common.collect.ArrayListMultimap;
13 import com.google.common.collect.BiMap;
14 import com.google.common.collect.Collections2;
15 import com.google.common.collect.ImmutableBiMap;
16 import com.google.common.collect.ImmutableBiMap.Builder;
17 import com.google.common.collect.ImmutableMap;
18 import com.google.common.collect.ImmutableSet;
19 import com.google.common.collect.Multimap;
20 import java.util.Collection;
22 import java.util.Map.Entry;
24 import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
25 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
26 import javax.annotation.concurrent.GuardedBy;
27 import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
28 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
29 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
30 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerBusyException;
31 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerException;
32 import org.opendaylight.mdsal.dom.api.DOMDataTreeShard;
33 import org.opendaylight.mdsal.dom.store.inmemory.DOMDataTreeShardProducer;
34 import org.opendaylight.mdsal.dom.store.inmemory.WriteableDOMDataTreeShard;
35 import org.opendaylight.yangtools.yang.data.api.YangInstanceIdentifier;
36 import org.slf4j.Logger;
37 import org.slf4j.LoggerFactory;
39 class ShardedDOMDataTreeProducer implements DOMDataTreeProducer {
40 private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTreeProducer.class);
42 private final Set<DOMDataTreeIdentifier> subtrees;
43 private final ShardedDOMDataTree dataTree;
45 private BiMap<DOMDataTreeIdentifier, DOMDataTreeShardProducer> idToProducer = ImmutableBiMap.of();
46 private Map<DOMDataTreeIdentifier, DOMDataTreeShard> idToShard;
48 private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
49 CURRENT_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
50 ShardedDOMDataTreeWriteTransaction.class, "currentTx");
51 private volatile ShardedDOMDataTreeWriteTransaction currentTx;
53 private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
54 OPEN_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
55 ShardedDOMDataTreeWriteTransaction.class, "openTx");
56 private volatile ShardedDOMDataTreeWriteTransaction openTx;
58 private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
59 LAST_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
60 ShardedDOMDataTreeWriteTransaction.class, "lastTx");
61 private volatile ShardedDOMDataTreeWriteTransaction lastTx;
63 private static final AtomicIntegerFieldUpdater<ShardedDOMDataTreeProducer> CLOSED_UPDATER =
64 AtomicIntegerFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class, "closed");
65 private volatile int closed;
68 private Map<DOMDataTreeIdentifier, DOMDataTreeProducer> children = ImmutableMap.of();
70 private Set<YangInstanceIdentifier> childRoots = ImmutableSet.of();
73 private ShardedDOMDataTreeListenerContext<?> attachedListener;
75 ShardedDOMDataTreeProducer(final ShardedDOMDataTree dataTree,
76 final Collection<DOMDataTreeIdentifier> subtrees,
77 final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap,
78 final Multimap<DOMDataTreeShard, DOMDataTreeIdentifier> shardToId) {
79 this.dataTree = Preconditions.checkNotNull(dataTree);
80 if (!shardToId.isEmpty()) {
81 this.idToProducer = mapIdsToProducer(shardToId);
83 idToShard = ImmutableMap.copyOf(shardMap);
84 this.subtrees = ImmutableSet.copyOf(subtrees);
87 static DOMDataTreeProducer create(final ShardedDOMDataTree dataTree,
88 final Collection<DOMDataTreeIdentifier> subtrees,
89 final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
90 final Multimap<DOMDataTreeShard, DOMDataTreeIdentifier> shardToIdentifiers = ArrayListMultimap.create();
91 // map which identifier belongs to which shard
92 for (final Entry<DOMDataTreeIdentifier, DOMDataTreeShard> entry : shardMap.entrySet()) {
93 shardToIdentifiers.put(entry.getValue(), entry.getKey());
96 return new ShardedDOMDataTreeProducer(dataTree, subtrees, shardMap, shardToIdentifiers);
99 private static BiMap<DOMDataTreeIdentifier, DOMDataTreeShardProducer> mapIdsToProducer(
100 final Multimap<DOMDataTreeShard, DOMDataTreeIdentifier> shardToId) {
101 final Builder<DOMDataTreeIdentifier, DOMDataTreeShardProducer> idToProducerBuilder = ImmutableBiMap.builder();
102 for (final Entry<DOMDataTreeShard, Collection<DOMDataTreeIdentifier>> entry : shardToId.asMap().entrySet()) {
103 if (entry.getKey() instanceof WriteableDOMDataTreeShard) {
104 //create a single producer for all prefixes in a single shard
105 final DOMDataTreeShardProducer producer = ((WriteableDOMDataTreeShard) entry.getKey())
106 .createProducer(entry.getValue());
107 // id mapped to producers
108 for (final DOMDataTreeIdentifier id : entry.getValue()) {
109 idToProducerBuilder.put(id, producer);
112 LOG.error("Unable to create a producer for shard that's not a WriteableDOMDataTreeShard");
116 return idToProducerBuilder.build();
119 private void checkNotClosed() {
120 Preconditions.checkState(closed == 0, "Producer is already closed");
123 private void checkIdle() {
124 Preconditions.checkState(openTx == null, "Transaction %s is still open", openTx);
127 void subshardAdded(final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
130 final Multimap<DOMDataTreeShard, DOMDataTreeIdentifier> shardToIdentifiers = ArrayListMultimap.create();
131 // map which identifier belongs to which shard
132 for (final Entry<DOMDataTreeIdentifier, DOMDataTreeShard> entry : shardMap.entrySet()) {
133 shardToIdentifiers.put(entry.getValue(), entry.getKey());
135 this.idToProducer = mapIdsToProducer(shardToIdentifiers);
136 idToShard = ImmutableMap.copyOf(shardMap);
140 public DOMDataTreeCursorAwareTransaction createTransaction(final boolean isolated) {
144 final ShardedDOMDataTreeWriteTransaction ret;
145 LOG.debug("Creating transaction from producer {}", this);
147 final ShardedDOMDataTreeWriteTransaction current = CURRENT_UPDATER.getAndSet(this, null);
149 // Isolated case. If we have a previous transaction, submit it before returning this one.
150 synchronized (this) {
151 if (current != null) {
152 submitTransaction(current);
154 ret = new ShardedDOMDataTreeWriteTransaction(this, idToProducer, childRoots);
157 // Non-isolated case, see if we can reuse the transaction
158 if (current != null) {
159 LOG.debug("Reusing previous transaction {} since there is still a transaction inflight",
160 current.getIdentifier());
163 synchronized (this) {
164 ret = new ShardedDOMDataTreeWriteTransaction(this, idToProducer, childRoots);
169 final boolean success = OPEN_UPDATER.compareAndSet(this, null, ret);
170 Preconditions.checkState(success, "Illegal concurrent access to producer %s detected", this);
175 private void submitTransaction(final ShardedDOMDataTreeWriteTransaction current) {
177 current.doSubmit(this::transactionSuccessful, this::transactionFailed);
181 private boolean haveSubtree(final DOMDataTreeIdentifier subtree) {
182 for (final DOMDataTreeIdentifier i : idToShard.keySet()) {
183 if (i.contains(subtree)) {
192 private DOMDataTreeProducer lookupChild(final DOMDataTreeIdentifier domDataTreeIdentifier) {
193 for (final Entry<DOMDataTreeIdentifier, DOMDataTreeProducer> e : children.entrySet()) {
194 if (e.getKey().contains(domDataTreeIdentifier)) {
203 public DOMDataTreeProducer createProducer(final Collection<DOMDataTreeIdentifier> subtrees) {
207 for (final DOMDataTreeIdentifier s : subtrees) {
208 // Check if the subtree was visible at any time
209 Preconditions.checkArgument(haveSubtree(s), "Subtree %s was never available in producer %s", s, this);
210 // Check if the subtree has not been delegated to a child
211 final DOMDataTreeProducer child = lookupChild(s);
212 Preconditions.checkArgument(child == null, "Subtree %s is delegated to child producer %s", s, child);
214 // Check if part of the requested subtree is not delegated to a child.
215 for (final DOMDataTreeIdentifier c : children.keySet()) {
216 Preconditions.checkArgument(!s.contains(c),
217 "Subtree %s cannot be delegated as it is a superset of already-delegated %s", s, c);
221 synchronized (this) {
222 final DOMDataTreeProducer ret = dataTree.createProducer(this, subtrees);
223 final ImmutableMap.Builder<DOMDataTreeIdentifier, DOMDataTreeProducer> cb = ImmutableMap.builder();
225 for (final DOMDataTreeIdentifier s : subtrees) {
229 children = cb.build();
230 childRoots = ImmutableSet.copyOf(Collections2.transform(children.keySet(),
231 DOMDataTreeIdentifier::getRootIdentifier));
236 boolean isDelegatedToChild(final DOMDataTreeIdentifier path) {
237 for (final DOMDataTreeIdentifier c : children.keySet()) {
238 if (c.contains(path)) {
247 public void close() throws DOMDataTreeProducerException {
248 if (openTx != null) {
249 throw new DOMDataTreeProducerBusyException(String.format("Transaction %s is still open", openTx));
252 if (CLOSED_UPDATER.compareAndSet(this, 0, 1)) {
253 synchronized (this) {
254 dataTree.destroyProducer(this);
259 protected Set<DOMDataTreeIdentifier> getSubtrees() {
263 void cancelTransaction(final ShardedDOMDataTreeWriteTransaction transaction) {
264 final boolean success = OPEN_UPDATER.compareAndSet(this, transaction, null);
266 LOG.debug("Transaction {} cancelled", transaction);
268 LOG.warn("Transaction {} is not open in producer {}", transaction, this);
272 // Called when the user submits a transaction
273 void transactionSubmitted(final ShardedDOMDataTreeWriteTransaction transaction) {
274 final boolean wasOpen = OPEN_UPDATER.compareAndSet(this, transaction, null);
275 Preconditions.checkState(wasOpen, "Attempted to submit non-open transaction %s", transaction);
277 if (lastTx == null) {
278 // No transaction outstanding, we need to submit it now
279 synchronized (this) {
280 submitTransaction(transaction);
286 // There is a potentially-racing submitted transaction. Publish the new one, which may end up being
287 // picked up by processNextTransaction.
288 final boolean success = CURRENT_UPDATER.compareAndSet(this, null, transaction);
289 Verify.verify(success);
291 // Now a quick check: if the racing transaction completed in between, it may have missed the current
292 // transaction, hence we need to re-check
293 if (lastTx == null) {
294 submitCurrentTransaction();
298 private void submitCurrentTransaction() {
299 final ShardedDOMDataTreeWriteTransaction current = currentTx;
300 if (current != null) {
301 synchronized (this) {
302 if (CURRENT_UPDATER.compareAndSet(this, current, null)) {
303 submitTransaction(current);
309 private void transactionSuccessful(final ShardedDOMDataTreeWriteTransaction tx) {
310 LOG.debug("Transaction {} completed successfully", tx.getIdentifier());
312 tx.onTransactionSuccess(null);
313 transactionCompleted(tx);
316 private void transactionFailed(final ShardedDOMDataTreeWriteTransaction tx, final Throwable throwable) {
317 LOG.debug("Transaction {} failed", tx.getIdentifier(), throwable);
319 tx.onTransactionFailure(throwable);
320 transactionCompleted(tx);
323 private void transactionCompleted(final ShardedDOMDataTreeWriteTransaction tx) {
324 final boolean wasLast = LAST_UPDATER.compareAndSet(this, tx, null);
326 submitCurrentTransaction();
330 synchronized void boundToListener(final ShardedDOMDataTreeListenerContext<?> listener) {
331 // FIXME: Add option to detach
332 Preconditions.checkState(this.attachedListener == null, "Producer %s is already attached to other listener.",
333 listener.getListener());
334 this.attachedListener = listener;