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