Do not generate 'isFoo()' methods
[mdsal.git] / dom / mdsal-dom-broker / src / main / java / org / opendaylight / mdsal / dom / broker / ShardedDOMDataTreeProducer.java
1 /*
2  * Copyright (c) 2015 Cisco Systems, Inc. and others.  All rights reserved.
3  *
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
7  */
8 package org.opendaylight.mdsal.dom.broker;
9
10 import static java.util.Objects.requireNonNull;
11
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;
16 import java.util.Map;
17 import java.util.Set;
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;
32
33 class ShardedDOMDataTreeProducer implements DOMDataTreeProducer {
34     private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTreeProducer.class);
35
36     private final Set<DOMDataTreeIdentifier> subtrees;
37     private final ShardedDOMDataTree dataTree;
38
39     private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
40         CURRENT_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
41             ShardedDOMDataTreeWriteTransaction.class, "currentTx");
42     private volatile ShardedDOMDataTreeWriteTransaction currentTx;
43
44     private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
45         OPEN_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
46             ShardedDOMDataTreeWriteTransaction.class, "openTx");
47     private volatile ShardedDOMDataTreeWriteTransaction openTx;
48
49     private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
50         LAST_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
51             ShardedDOMDataTreeWriteTransaction.class, "lastTx");
52     private volatile ShardedDOMDataTreeWriteTransaction lastTx;
53
54     private static final AtomicIntegerFieldUpdater<ShardedDOMDataTreeProducer> CLOSED_UPDATER =
55             AtomicIntegerFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class, "closed");
56     private volatile int closed;
57
58     private volatile DOMDataTreeListener attachedListener;
59     private volatile ProducerLayout layout;
60
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);
67     }
68
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);
73     }
74
75     private void checkNotClosed() {
76         Preconditions.checkState(closed == 0, "Producer is already closed");
77     }
78
79     private void checkIdle() {
80         Preconditions.checkState(openTx == null, "Transaction %s is still open", openTx);
81     }
82
83     void subshardAdded(final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
84         checkIdle();
85         layout = layout.reshard(shardMap);
86     }
87
88     @Override
89     public DOMDataTreeCursorAwareTransaction createTransaction(final boolean isolated) {
90         checkNotClosed();
91         checkIdle();
92
93         LOG.debug("Creating transaction from producer {}", this);
94
95         final ShardedDOMDataTreeWriteTransaction current = CURRENT_UPDATER.getAndSet(this, null);
96         final ShardedDOMDataTreeWriteTransaction ret;
97         if (isolated) {
98             ret = createIsolatedTransaction(layout, current);
99         } else {
100             ret = createReusedTransaction(layout, current);
101         }
102
103         final boolean success = OPEN_UPDATER.compareAndSet(this, null, ret);
104         Preconditions.checkState(success, "Illegal concurrent access to producer %s detected", this);
105         return ret;
106     }
107
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.
110     @GuardedBy("this")
111     private ShardedDOMDataTreeWriteTransaction createTransaction(final ProducerLayout local) {
112         return new ShardedDOMDataTreeWriteTransaction(this, local.createTransactions(), local);
113
114     }
115
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);
121         }
122
123         return createTransaction(local);
124     }
125
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());
133                 return current;
134             }
135
136             synchronized (this) {
137                 submitTransaction(current);
138                 return createTransaction(local);
139             }
140         }
141
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);
146         }
147     }
148
149     @Holding("this")
150     private void submitTransaction(final ShardedDOMDataTreeWriteTransaction tx) {
151         lastTx = tx;
152         tx.doSubmit(this::transactionSuccessful, this::transactionFailed);
153     }
154
155     @Override
156     @SuppressWarnings("checkstyle:hiddenField")
157     public DOMDataTreeProducer createProducer(final Collection<DOMDataTreeIdentifier> subtrees) {
158         checkNotClosed();
159         checkIdle();
160
161         final ProducerLayout local = layout;
162
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);
169
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);
174             }
175         }
176
177
178         final DOMDataTreeProducer ret;
179         synchronized (this) {
180             ret = dataTree.createProducer(this, subtrees);
181         }
182
183         layout = local.addChild(ret, subtrees);
184         return ret;
185     }
186
187     boolean isDelegatedToChild(final DOMDataTreeIdentifier path) {
188         return layout.lookupChild(path) != null;
189     }
190
191     @Override
192     public void close() throws DOMDataTreeProducerException {
193         if (openTx != null) {
194             throw new DOMDataTreeProducerBusyException(String.format("Transaction %s is still open", openTx));
195         }
196
197         if (CLOSED_UPDATER.compareAndSet(this, 0, 1)) {
198             synchronized (this) {
199                 dataTree.destroyProducer(this);
200                 layout.close();
201             }
202         }
203     }
204
205     protected Set<DOMDataTreeIdentifier> getSubtrees() {
206         return subtrees;
207     }
208
209     void cancelTransaction(final ShardedDOMDataTreeWriteTransaction transaction) {
210         final boolean success = OPEN_UPDATER.compareAndSet(this, transaction, null);
211         if (success) {
212             LOG.debug("Transaction {} cancelled", transaction);
213         } else {
214             LOG.warn("Transaction {} is not open in producer {}", transaction, this);
215         }
216     }
217
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);
222
223         if (lastTx == null) {
224             // No transaction outstanding, we need to submit it now
225             synchronized (this) {
226                 submitTransaction(transaction);
227             }
228
229             return;
230         }
231
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);
236
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();
241         }
242     }
243
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);
250                 }
251             }
252         }
253     }
254
255     private void transactionSuccessful(final ShardedDOMDataTreeWriteTransaction tx) {
256         LOG.debug("Transaction {} completed successfully", tx.getIdentifier());
257
258         tx.onTransactionSuccess(CommitInfo.empty());
259         transactionCompleted(tx);
260     }
261
262     private void transactionFailed(final ShardedDOMDataTreeWriteTransaction tx, final Throwable throwable) {
263         LOG.debug("Transaction {} failed", tx.getIdentifier(), throwable);
264
265         tx.onTransactionFailure(throwable);
266         // FIXME: transaction failure should result in a hard error
267         transactionCompleted(tx);
268     }
269
270     private void transactionCompleted(final ShardedDOMDataTreeWriteTransaction tx) {
271         final boolean wasLast = LAST_UPDATER.compareAndSet(this, tx, null);
272         if (wasLast) {
273             submitCurrentTransaction();
274         }
275     }
276
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);
281     }
282 }