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