001/**
002 * The MIT License (MIT)
003 *
004 * Copyright (c) 2018 nobark (tools4j), Marco Terzer, Anton Anufriev
005 *
006 * Permission is hereby granted, free of charge, to any person obtaining a copy
007 * of this software and associated documentation files (the "Software"), to deal
008 * in the Software without restriction, including without limitation the rights
009 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
010 * copies of the Software, and to permit persons to whom the Software is
011 * furnished to do so, subject to the following conditions:
012 *
013 * The above copyright notice and this permission notice shall be included in all
014 * copies or substantial portions of the Software.
015 *
016 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
017 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
018 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
019 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
020 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
021 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
022 * SOFTWARE.
023 */
024package org.tools4j.nobark.queue;
025
026import sun.misc.Contended;
027
028import java.util.List;
029import java.util.Map;
030import java.util.Objects;
031import java.util.Queue;
032import java.util.concurrent.ConcurrentHashMap;
033import java.util.function.BiConsumer;
034import java.util.function.Supplier;
035
036/**
037 * A conflation queue implementation that evicts old values from the queue if a new one is enqueued with the same
038 * conflation key.  The {@link AtomicConflationQueue} is very similar but this queue implements
039 * {@link ExchangeConflationQueue} and hence supports value exchange between producer and consumer.  The backing queue
040 * is supplied to the constructor and it determines whether single or multiple producers and consumers are supported.
041 * <p>
042 * Each producer should acquire its own {@link #appender() appender} from the producing thread, and similarly every
043 * consumer should call {@link #poller()} from the consuming thread.  Note that appender listener and poller listener
044 * must be thread safe if multiple producers or consumers are used, e.g. use
045 * {@link AppenderListener#threadLocalSupplier(Supplier)} and {@link PollerListener#threadLocalSupplier(Supplier)} to
046 * create separate listener instances per producer/consumer thread.
047 *
048 * @param <K> the type of the conflation key
049 * @param <V> the type of elements in the queue
050 */
051public class EvictConflationQueue<K,V> implements ExchangeConflationQueue<K,V> {
052
053    private final Queue<Entry<K,MarkedValue<V>>> queue;
054    private final Map<K,Entry<K,MarkedValue<V>>> entryMap;
055
056    private final ThreadLocal<Appender<K,V>> appender = ThreadLocal.withInitial(EvictQueueAppender::new);
057    private final ThreadLocal<ExchangePoller<K,V>> poller = ThreadLocal.withInitial(EvictQueuePoller::new);
058
059    private final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier;
060    private final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier;
061
062    private EvictConflationQueue(final Queue<Entry<K,MarkedValue<V>>> queue,
063                                 final Map<K,Entry<K,MarkedValue<V>>> entryMap,
064                                 final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier,
065                                 final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) {
066        this.queue = Objects.requireNonNull(queue);
067        this.entryMap = Objects.requireNonNull(entryMap);
068        this.appenderListenerSupplier = Objects.requireNonNull(appenderListenerSupplier);
069        this.pollerListenerSupplier = Objects.requireNonNull(pollerListenerSupplier);
070    }
071
072    /**
073     * Constructor with queue factory.  A concurrent hash map is used to manage entries per conflation key.
074     *
075     * @param queueFactory the factory to create the backing queue
076     */
077    public EvictConflationQueue(final Supplier<? extends Queue<Object>> queueFactory) {
078        this(queueFactory, () -> AppenderListener.NOOP, () -> PollerListener.NOOP);
079    }
080
081    /**
082     * Constructor with queue factory.  A concurrent hash map is used to manage entries per conflation key.
083     *
084     * @param queueFactory              the factory to create the backing queue
085     * @param appenderListenerSupplier  a supplier for a listener to monitor the enqueue operations
086     * @param pollerListenerSupplier    a supplier for a listener to monitor the poll operations
087     */
088    public EvictConflationQueue(final Supplier<? extends Queue<Object>> queueFactory,
089                                final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier,
090                                final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) {
091        this(queueFactory, ConcurrentHashMap::new, appenderListenerSupplier, pollerListenerSupplier);
092    }
093
094    /**
095     * Constructor with queue factory and entry map factory.
096     *
097     * @param queueFactory      the factory to create the backing queue
098     * @param entryMapFactory   the factory to create the map that manages entries per conflation key
099     * @param appenderListenerSupplier  a supplier for a listener to monitor the enqueue operations
100     * @param pollerListenerSupplier    a supplier for a listener to monitor the poll operations
101     */
102    public EvictConflationQueue(final Supplier<? extends Queue<Object>> queueFactory,
103                                final Supplier<? extends Map<Object, Object>> entryMapFactory,
104                                final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier,
105                                final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) {
106        this(Factories.createQueue(queueFactory), Factories.createMap(entryMapFactory), appenderListenerSupplier,
107                pollerListenerSupplier);
108    }
109
110    /**
111     * Constructor with queue factory and the exhaustive list of conflation keys.  A hash map is pre-initialized with
112     * all the conflation keys and pre-allocated entries.
113     *
114     * @param queueFactory the factory to create the backing queue
115     * @param allConflationKeys all conflation keys that will ever be used with this conflation queue instance
116     */
117    public EvictConflationQueue(final Supplier<? extends Queue<Object>> queueFactory,
118                                final List<? extends K> allConflationKeys) {
119        this(queueFactory, allConflationKeys, () -> AppenderListener.NOOP, () -> PollerListener.NOOP);
120    }
121
122    /**
123     * Constructor with queue factory and the exhaustive list of conflation keys.  A hash map is pre-initialized with
124     * all the conflation keys and pre-allocated entries.
125     *
126     * @param queueFactory the factory to create the backing queue
127     * @param allConflationKeys all conflation keys that will ever be used with this conflation queue instance
128     * @param appenderListenerSupplier a supplier for a listener to monitor the enqueue operations
129     * @param pollerListenerSupplier a supplier for a listener to monitor the poll operations
130     */
131    public EvictConflationQueue(final Supplier<? extends Queue<Object>> queueFactory,
132                                final List<? extends K> allConflationKeys,
133                                final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier,
134                                final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) {
135        this(Factories.createQueue(queueFactory), Entry.eagerlyInitialiseEntryMap(allConflationKeys, MarkedValue::new),
136                appenderListenerSupplier, pollerListenerSupplier);
137    }
138
139    /**
140     * Static constructor method for a conflation queue with queue factory and the conflation key enum class.  An enum
141     * map is pre-initialized with all the conflation keys and pre-allocated entries.
142     *
143     * @param queueFactory the factory to create the backing queue
144     * @param conflationKeyClass the conflation key enum class
145     * @param <K> the type of the conflation key
146     * @param <V> the type of elements in the queue
147     * @return the new conflation queue instance
148     */
149    public static <K extends Enum<K>,V> EvictConflationQueue<K,V> forEnumConflationKey(final Supplier<? extends Queue<Object>> queueFactory,
150                                                                                       final Class<K> conflationKeyClass) {
151        return forEnumConflationKey(queueFactory, conflationKeyClass, () -> AppenderListener.NOOP, () -> PollerListener.NOOP);
152    }
153
154    /**
155     * Static constructor method for a conflation queue with queue factory and the conflation key enum class.  An enum
156     * map is pre-initialized with all the conflation keys and pre-allocated entries.
157     *
158     * @param queueFactory the factory to create the backing queue
159     * @param conflationKeyClass the conflation key enum class
160     * @param appenderListenerSupplier a supplier for a listener to monitor the enqueue operations
161     * @param pollerListenerSupplier a supplier for a listener to monitor the poll operations
162     * @param <K> the type of the conflation key
163     * @param <V> the type of elements in the queue
164     * @return the new conflation queue instance
165     */
166    public static <K extends Enum<K>,V> EvictConflationQueue<K,V> forEnumConflationKey(final Supplier<? extends Queue<Object>> queueFactory,
167                                                                                       final Class<K> conflationKeyClass,
168                                                                                       final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier,
169                                                                                       final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) {
170        return new EvictConflationQueue<>(
171                Factories.createQueue(queueFactory), Entry.eagerlyInitialiseEntryEnumMap(conflationKeyClass, MarkedValue::new),
172                appenderListenerSupplier, pollerListenerSupplier
173        );
174    }
175
176    @Override
177    public Appender<K, V> appender() {
178        return appender.get();
179    }
180
181    @Override
182    public ExchangePoller<K, V> poller() {
183        return poller.get();
184    }
185
186    @Override
187    public int size() {
188        return queue.size();
189    }
190
191    @Contended
192    private final static class MarkedValue<V> {
193        enum State {USED, UNUSED}
194        V value;
195        State state = State.UNUSED;
196        MarkedValue<V> initializeWithUsed(final V value) {
197            this.value = Objects.requireNonNull(value);
198            this.state = State.USED;
199            return this;
200        }
201        MarkedValue<V> initalizeWithUnused(final V value) {
202            this.value = value;//nulls allowed here
203            this.state = State.UNUSED;
204            return this;
205        }
206
207        V markUnusedAndRelease() {
208            final V released = value;
209            state = State.UNUSED;
210            value = null;
211            return released;
212        }
213
214        boolean isUnused() {
215            return state == State.UNUSED;
216        }
217    }
218
219    private final class EvictQueueAppender implements Appender<K,V> {
220        final AppenderListener<? super K, ? super V> appenderListener = appenderListenerSupplier.get();
221        @Contended
222        MarkedValue<V> markedValue = new MarkedValue<>();
223        @Override
224        public V enqueue(final K conflationKey, final V value) {
225            Objects.requireNonNull(value);
226            final Entry<K, MarkedValue<V>> entry = entryMap.computeIfAbsent(conflationKey, k -> new Entry<>(k, new MarkedValue<>()));
227            final MarkedValue<V> newValue = markedValue.initializeWithUsed(value);
228            final MarkedValue<V> oldValue = entry.value.getAndSet(newValue);
229            final AppenderListener.Conflation conflation;
230            if (oldValue.isUnused()) {
231                queue.add(entry);
232                conflation = AppenderListener.Conflation.UNCONFLATED;
233            } else {
234                conflation = AppenderListener.Conflation.EVICTED;
235            }
236            markedValue = oldValue;
237            final V old = oldValue.markUnusedAndRelease();
238            //NOTE: ok if listener throws exception now as it cannot mess up this queue's state
239            appenderListener.enqueued(EvictConflationQueue.this, conflationKey, value, old, conflation);
240            return old;
241        }
242    }
243
244    private final class EvictQueuePoller implements ExchangePoller<K,V> {
245        final PollerListener<? super K, ? super V> pollerListener = pollerListenerSupplier.get();
246        @Contended
247        MarkedValue<V> markedValue = new MarkedValue<>();
248        @Override
249        public V poll(final BiConsumer<? super K, ? super V> consumer, final V exchange) {
250            final Entry<K,MarkedValue<V>> entry = queue.poll();
251            if (entry != null) {
252                final MarkedValue<V> exchangeValue = markedValue.initalizeWithUnused(exchange);
253                final MarkedValue<V> polledValue = entry.value.getAndSet(exchangeValue);
254                final V value = polledValue.markUnusedAndRelease();
255                markedValue = polledValue;
256                consumer.accept(entry.key, value);
257                pollerListener.polled(EvictConflationQueue.this, entry.key, value);
258                return value;
259            } else {
260                pollerListener.polledButFoundEmpty(EvictConflationQueue.this);
261                return null;
262            }
263        }
264    }
265}