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