Switch to Objects.requireNonNull
[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 javax.annotation.concurrent.GuardedBy;
21 import org.opendaylight.mdsal.common.api.CommitInfo;
22 import org.opendaylight.mdsal.dom.api.DOMDataTreeCursorAwareTransaction;
23 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
24 import org.opendaylight.mdsal.dom.api.DOMDataTreeListener;
25 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducer;
26 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerBusyException;
27 import org.opendaylight.mdsal.dom.api.DOMDataTreeProducerException;
28 import org.opendaylight.mdsal.dom.api.DOMDataTreeShard;
29 import org.slf4j.Logger;
30 import org.slf4j.LoggerFactory;
31
32 class ShardedDOMDataTreeProducer implements DOMDataTreeProducer {
33     private static final Logger LOG = LoggerFactory.getLogger(ShardedDOMDataTreeProducer.class);
34
35     private final Set<DOMDataTreeIdentifier> subtrees;
36     private final ShardedDOMDataTree dataTree;
37
38     private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
39         CURRENT_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
40             ShardedDOMDataTreeWriteTransaction.class, "currentTx");
41     private volatile ShardedDOMDataTreeWriteTransaction currentTx;
42
43     private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
44         OPEN_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
45             ShardedDOMDataTreeWriteTransaction.class, "openTx");
46     private volatile ShardedDOMDataTreeWriteTransaction openTx;
47
48     private static final AtomicReferenceFieldUpdater<ShardedDOMDataTreeProducer, ShardedDOMDataTreeWriteTransaction>
49         LAST_UPDATER = AtomicReferenceFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class,
50             ShardedDOMDataTreeWriteTransaction.class, "lastTx");
51     private volatile ShardedDOMDataTreeWriteTransaction lastTx;
52
53     private static final AtomicIntegerFieldUpdater<ShardedDOMDataTreeProducer> CLOSED_UPDATER =
54             AtomicIntegerFieldUpdater.newUpdater(ShardedDOMDataTreeProducer.class, "closed");
55     private volatile int closed;
56
57     private volatile DOMDataTreeListener attachedListener;
58     private volatile ProducerLayout layout;
59
60     ShardedDOMDataTreeProducer(final ShardedDOMDataTree dataTree,
61                                final Collection<DOMDataTreeIdentifier> subtrees,
62                                final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
63         this.dataTree = requireNonNull(dataTree);
64         this.subtrees = ImmutableSet.copyOf(subtrees);
65         this.layout = ProducerLayout.create(shardMap);
66     }
67
68     static DOMDataTreeProducer create(final ShardedDOMDataTree dataTree,
69                                       final Collection<DOMDataTreeIdentifier> subtrees,
70                                       final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
71         return new ShardedDOMDataTreeProducer(dataTree, subtrees, shardMap);
72     }
73
74     private void checkNotClosed() {
75         Preconditions.checkState(closed == 0, "Producer is already closed");
76     }
77
78     private void checkIdle() {
79         Preconditions.checkState(openTx == null, "Transaction %s is still open", openTx);
80     }
81
82     void subshardAdded(final Map<DOMDataTreeIdentifier, DOMDataTreeShard> shardMap) {
83         checkIdle();
84         layout = layout.reshard(shardMap);
85     }
86
87     @Override
88     public DOMDataTreeCursorAwareTransaction createTransaction(final boolean isolated) {
89         checkNotClosed();
90         checkIdle();
91
92         LOG.debug("Creating transaction from producer {}", this);
93
94         final ShardedDOMDataTreeWriteTransaction current = CURRENT_UPDATER.getAndSet(this, null);
95         final ShardedDOMDataTreeWriteTransaction ret;
96         if (isolated) {
97             ret = createIsolatedTransaction(layout, current);
98         } else {
99             ret = createReusedTransaction(layout, current);
100         }
101
102         final boolean success = OPEN_UPDATER.compareAndSet(this, null, ret);
103         Preconditions.checkState(success, "Illegal concurrent access to producer %s detected", this);
104         return ret;
105     }
106
107     // This may look weird, but this has side-effects on local's producers, hence it needs to be properly synchronized
108     // so that it happens-after submitTransaction() which may have been stolen by a callback.
109     @GuardedBy("this")
110     private ShardedDOMDataTreeWriteTransaction createTransaction(final ProducerLayout local) {
111         return new ShardedDOMDataTreeWriteTransaction(this, local.createTransactions(), local);
112
113     }
114
115     // Isolated case. If we have a previous transaction, submit it before returning this one.
116     private synchronized ShardedDOMDataTreeWriteTransaction createIsolatedTransaction(
117             final ProducerLayout local, final ShardedDOMDataTreeWriteTransaction current) {
118         if (current != null) {
119             submitTransaction(current);
120         }
121
122         return createTransaction(local);
123     }
124
125     private ShardedDOMDataTreeWriteTransaction createReusedTransaction(final ProducerLayout local,
126             final ShardedDOMDataTreeWriteTransaction current) {
127         if (current != null) {
128             // Lock-free fast path
129             if (local.equals(current.getLayout())) {
130                 LOG.debug("Reusing previous transaction {} since there is still a transaction inflight",
131                     current.getIdentifier());
132                 return current;
133             }
134
135             synchronized (this) {
136                 submitTransaction(current);
137                 return createTransaction(local);
138             }
139         }
140
141         // Null indicates we have not seen a previous transaction -- which does not mean it is ready, as it may have
142         // been stolen and in is process of being submitted.
143         synchronized (this) {
144             return createTransaction(local);
145         }
146     }
147
148     @GuardedBy("this")
149     private void submitTransaction(final ShardedDOMDataTreeWriteTransaction tx) {
150         lastTx = tx;
151         tx.doSubmit(this::transactionSuccessful, this::transactionFailed);
152     }
153
154     @Override
155     @SuppressWarnings("checkstyle:hiddenField")
156     public DOMDataTreeProducer createProducer(final Collection<DOMDataTreeIdentifier> subtrees) {
157         checkNotClosed();
158         checkIdle();
159
160         final ProducerLayout local = layout;
161
162         for (final DOMDataTreeIdentifier s : subtrees) {
163             // Check if the subtree was visible at any time
164             Preconditions.checkArgument(local.haveSubtree(s), "Subtree %s was never available in producer %s", s, this);
165             // Check if the subtree has not been delegated to a child
166             final DOMDataTreeProducer child = local.lookupChild(s);
167             Preconditions.checkArgument(child == null, "Subtree %s is delegated to child producer %s", s, child);
168
169             // Check if part of the requested subtree is not delegated to a child.
170             for (final DOMDataTreeIdentifier c : local.getChildTrees()) {
171                 Preconditions.checkArgument(!s.contains(c),
172                     "Subtree %s cannot be delegated as it is a superset of already-delegated %s", s, c);
173             }
174         }
175
176
177         final DOMDataTreeProducer ret;
178         synchronized (this) {
179             ret = dataTree.createProducer(this, subtrees);
180         }
181
182         layout = local.addChild(ret, subtrees);
183         return ret;
184     }
185
186     boolean isDelegatedToChild(final DOMDataTreeIdentifier path) {
187         return layout.lookupChild(path) != null;
188     }
189
190     @Override
191     public void close() throws DOMDataTreeProducerException {
192         if (openTx != null) {
193             throw new DOMDataTreeProducerBusyException(String.format("Transaction %s is still open", openTx));
194         }
195
196         if (CLOSED_UPDATER.compareAndSet(this, 0, 1)) {
197             synchronized (this) {
198                 dataTree.destroyProducer(this);
199                 layout.close();
200             }
201         }
202     }
203
204     protected Set<DOMDataTreeIdentifier> getSubtrees() {
205         return subtrees;
206     }
207
208     void cancelTransaction(final ShardedDOMDataTreeWriteTransaction transaction) {
209         final boolean success = OPEN_UPDATER.compareAndSet(this, transaction, null);
210         if (success) {
211             LOG.debug("Transaction {} cancelled", transaction);
212         } else {
213             LOG.warn("Transaction {} is not open in producer {}", transaction, this);
214         }
215     }
216
217     // Called when the user submits a transaction
218     void transactionSubmitted(final ShardedDOMDataTreeWriteTransaction transaction) {
219         final boolean wasOpen = OPEN_UPDATER.compareAndSet(this, transaction, null);
220         Preconditions.checkState(wasOpen, "Attempted to submit non-open transaction %s", transaction);
221
222         if (lastTx == null) {
223             // No transaction outstanding, we need to submit it now
224             synchronized (this) {
225                 submitTransaction(transaction);
226             }
227
228             return;
229         }
230
231         // There is a potentially-racing submitted transaction. Publish the new one, which may end up being
232         // picked up by processNextTransaction.
233         final boolean success = CURRENT_UPDATER.compareAndSet(this, null, transaction);
234         Verify.verify(success);
235
236         // Now a quick check: if the racing transaction completed in between, it may have missed the current
237         // transaction, hence we need to re-check
238         if (lastTx == null) {
239             submitCurrentTransaction();
240         }
241     }
242
243     private void submitCurrentTransaction() {
244         final ShardedDOMDataTreeWriteTransaction current = currentTx;
245         if (current != null) {
246             synchronized (this) {
247                 if (CURRENT_UPDATER.compareAndSet(this, current, null)) {
248                     submitTransaction(current);
249                 }
250             }
251         }
252     }
253
254     private void transactionSuccessful(final ShardedDOMDataTreeWriteTransaction tx) {
255         LOG.debug("Transaction {} completed successfully", tx.getIdentifier());
256
257         tx.onTransactionSuccess(CommitInfo.empty());
258         transactionCompleted(tx);
259     }
260
261     private void transactionFailed(final ShardedDOMDataTreeWriteTransaction tx, final Throwable throwable) {
262         LOG.debug("Transaction {} failed", tx.getIdentifier(), throwable);
263
264         tx.onTransactionFailure(throwable);
265         // FIXME: transaction failure should result in a hard error
266         transactionCompleted(tx);
267     }
268
269     private void transactionCompleted(final ShardedDOMDataTreeWriteTransaction tx) {
270         final boolean wasLast = LAST_UPDATER.compareAndSet(this, tx, null);
271         if (wasLast) {
272             submitCurrentTransaction();
273         }
274     }
275
276     void bindToListener(final DOMDataTreeListener listener) {
277         final DOMDataTreeListener local = attachedListener;
278         Preconditions.checkState(local == null, "Producer %s is already attached to listener %s", this, local);
279         this.attachedListener = requireNonNull(listener);
280     }
281 }