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