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