Fix up release old producers to avoid memory leak
[mdsal.git] / dom / mdsal-dom-inmemory-datastore / src / main / java / org / opendaylight / mdsal / dom / store / inmemory / InMemoryDOMDataTreeShardProducer.java
1 /*
2  * Copyright (c) 2016 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.store.inmemory;
9
10 import static com.google.common.base.Preconditions.checkState;
11 import static java.util.Objects.requireNonNull;
12
13 import com.google.common.collect.ImmutableSet;
14 import java.util.Collection;
15 import java.util.concurrent.atomic.AtomicLong;
16 import java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
17 import org.eclipse.jdt.annotation.NonNull;
18 import org.opendaylight.mdsal.dom.api.DOMDataTreeIdentifier;
19 import org.opendaylight.mdsal.dom.spi.shard.DOMDataTreeShardProducer;
20 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeModification;
21 import org.opendaylight.yangtools.yang.data.api.schema.tree.DataTreeSnapshot;
22 import org.slf4j.Logger;
23 import org.slf4j.LoggerFactory;
24
25 class InMemoryDOMDataTreeShardProducer implements DOMDataTreeShardProducer {
26
27     private abstract static class State {
28         /**
29          * Allocate a new snapshot.
30          *
31          * @return A new snapshot
32          */
33         protected abstract DataTreeSnapshot getSnapshot(Object transactionId);
34     }
35
36     private static final class Idle extends State {
37         private final InMemoryDOMDataTreeShardProducer producer;
38
39         Idle(final InMemoryDOMDataTreeShardProducer producer) {
40             this.producer = requireNonNull(producer);
41         }
42
43         @Override
44         protected DataTreeSnapshot getSnapshot(final Object transactionId) {
45             return producer.takeSnapshot();
46         }
47     }
48
49     /**
50      * We have a transaction out there.
51      */
52     private static final class Allocated extends State {
53         private static final AtomicReferenceFieldUpdater<Allocated, DataTreeSnapshot> SNAPSHOT_UPDATER =
54                 AtomicReferenceFieldUpdater.newUpdater(Allocated.class, DataTreeSnapshot.class, "snapshot");
55         private final InmemoryDOMDataTreeShardWriteTransaction transaction;
56         private volatile DataTreeSnapshot snapshot;
57
58         Allocated(final InmemoryDOMDataTreeShardWriteTransaction transaction) {
59             this.transaction = requireNonNull(transaction);
60         }
61
62         InmemoryDOMDataTreeShardWriteTransaction getTransaction() {
63             return transaction;
64         }
65
66         @Override
67         protected DataTreeSnapshot getSnapshot(final Object transactionId) {
68             final DataTreeSnapshot ret = snapshot;
69             checkState(ret != null,
70                     "Could not get snapshot for transaction %s - previous transaction %s is not ready yet",
71                     transactionId, transaction.getIdentifier());
72             return ret;
73         }
74
75         void setSnapshot(final DataTreeSnapshot snapshot) {
76             final boolean success = SNAPSHOT_UPDATER.compareAndSet(this, null, snapshot);
77             checkState(success, "Transaction %s has already been marked as ready",
78                     transaction.getIdentifier());
79         }
80     }
81
82     /**
83      * Producer is logically shut down, no further allocation allowed.
84      */
85     private static final class Shutdown extends State {
86         private final String message;
87
88         Shutdown(final String message) {
89             this.message = requireNonNull(message);
90         }
91
92         @Override
93         protected DataTreeSnapshot getSnapshot(final Object transactionId) {
94             throw new IllegalStateException(message);
95         }
96     }
97
98     private static final Logger LOG = LoggerFactory.getLogger(InMemoryDOMDataTreeShardProducer.class);
99     private static final AtomicLong COUNTER = new AtomicLong();
100
101     private final InMemoryDOMDataTreeShard parentShard;
102     private final Collection<DOMDataTreeIdentifier> prefixes;
103     private final Idle idleState = new Idle(this);
104
105     private static final AtomicReferenceFieldUpdater<InMemoryDOMDataTreeShardProducer, State> STATE_UPDATER =
106             AtomicReferenceFieldUpdater.newUpdater(InMemoryDOMDataTreeShardProducer.class, State.class, "state");
107     private volatile State state;
108
109     private InMemoryShardDataModificationFactory modificationFactory;
110
111     InMemoryDOMDataTreeShardProducer(final InMemoryDOMDataTreeShard parentShard,
112             final Collection<DOMDataTreeIdentifier> prefixes,
113             final InMemoryShardDataModificationFactory modificationFactory) {
114         this.parentShard = requireNonNull(parentShard);
115         this.prefixes = ImmutableSet.copyOf(prefixes);
116         this.modificationFactory = requireNonNull(modificationFactory);
117         state = idleState;
118     }
119
120     @Override
121     public InmemoryDOMDataTreeShardWriteTransaction createTransaction() {
122         final String transactionId = nextIdentifier();
123
124         State localState;
125         InmemoryDOMDataTreeShardWriteTransaction ret;
126         do {
127             localState = state;
128             ret = parentShard.createTransaction(transactionId, this, localState.getSnapshot(transactionId));
129         } while (!STATE_UPDATER.compareAndSet(this, localState, new Allocated(ret)));
130
131         return ret;
132     }
133
134     @Override
135     public void close() {
136         final Shutdown shutdown = new Shutdown("Producer closed");
137         if (!STATE_UPDATER.compareAndSet(this, idleState, shutdown)) {
138             throw new IllegalStateException("Producer " + this + " in unexpected state " + state);
139         }
140
141         // FIXME: This call is ugly, it's better to clean up all by exposing only one entrance,
142         // 'closeProducer' of shard or this 'close'.
143         getParentShard().closeProducer(this);
144         getModificationFactory().close();
145     }
146
147     void transactionReady(final InmemoryDOMDataTreeShardWriteTransaction tx, final DataTreeModification modification) {
148         final State localState = state;
149         LOG.debug("Transaction was readied {}, current state {}", tx.getIdentifier(), localState);
150
151         if (localState instanceof Allocated) {
152             final Allocated allocated = (Allocated) localState;
153             final InmemoryDOMDataTreeShardWriteTransaction transaction = allocated.getTransaction();
154             checkState(tx.equals(transaction), "Mis-ordered ready transaction %s last allocated was %s", tx,
155                 transaction);
156             allocated.setSnapshot(modification);
157         } else {
158             LOG.debug("Ignoring transaction {} readiness due to state {}", tx, localState);
159         }
160     }
161
162     /**
163      * Notify the base logic that a previously-submitted transaction has been committed successfully.
164      *
165      * @param transaction Transaction which completed successfully.
166      */
167     void onTransactionCommited(final InmemoryDOMDataTreeShardWriteTransaction transaction) {
168         // If the committed transaction was the one we allocated last,
169         // we clear it and the ready snapshot, so the next transaction
170         // allocated refers to the data tree directly.
171         final State localState = state;
172         LOG.debug("Transaction {} commit done, current state {}", transaction.getIdentifier(), localState);
173
174         if (!(localState instanceof Allocated)) {
175             // This can legally happen if the chain is shut down before the transaction was committed
176             // by the backend.
177             LOG.debug("Ignoring successful transaction {} in state {}", transaction, localState);
178             return;
179         }
180
181         final Allocated allocated = (Allocated) localState;
182         final InmemoryDOMDataTreeShardWriteTransaction tx = allocated.getTransaction();
183         if (!tx.equals(transaction)) {
184             LOG.debug("Ignoring non-latest successful transaction {} in state {}", transaction, allocated);
185             return;
186         }
187
188         if (!STATE_UPDATER.compareAndSet(this, localState, idleState)) {
189             LOG.debug("Producer {} has already transitioned from {} to {}, not making it idle", this,
190                     localState, state);
191         }
192     }
193
194     void transactionAborted(final InmemoryDOMDataTreeShardWriteTransaction tx) {
195         final State localState = state;
196         if (localState instanceof Allocated) {
197             final Allocated allocated = (Allocated) localState;
198             if (allocated.getTransaction().equals(tx)) {
199                 final boolean success = STATE_UPDATER.compareAndSet(this, localState, idleState);
200                 if (!success) {
201                     LOG.warn("Transaction {} aborted, but producer {} state already transitioned from {} to {}",
202                             tx, this, localState, state);
203                 }
204             }
205         }
206     }
207
208     private static String nextIdentifier() {
209         return "INMEMORY-SHARD-TX-" + COUNTER.getAndIncrement();
210     }
211
212     DataTreeSnapshot takeSnapshot() {
213         return parentShard.takeSnapshot();
214     }
215
216     @Override
217     public Collection<DOMDataTreeIdentifier> getPrefixes() {
218         return prefixes;
219     }
220
221     @NonNull InMemoryDOMDataTreeShard getParentShard() {
222         return parentShard;
223     }
224
225     InMemoryShardDataModificationFactory getModificationFactory() {
226         return modificationFactory;
227     }
228
229     void setModificationFactory(final InMemoryShardDataModificationFactory modificationFactory) {
230         this.getModificationFactory().close();
231         this.modificationFactory = requireNonNull(modificationFactory);
232     }
233 }