001/**
002 * The MIT License (MIT)
003 *
004 * Copyright (c) 2019 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 merges old and new value if a value is enqueued and another one already exists
038 * in the queue with the same conflation key.  The backing queue is supplied to the constructor and it determines
039 * whether single or multiple producers and consumers are supported.
040 * <p>
041 * Each producer should acquire its own {@link #appender() appender} from the producing thread, and similarly every
042 * consumer should call {@link #poller()} from the consuming thread.  Note that appender listener and poller listener
043 * must be thread safe if multiple producers or consumers are used, e.g. use
044 * {@link AppenderListener#threadLocalSupplier(Supplier)} and {@link PollerListener#threadLocalSupplier(Supplier)} to
045 * create separate listener instances per producer/consumer thread.
046 *
047 * @param <K> the type of the conflation key
048 * @param <V> the type of elements in the queue
049 */
050public class MergeConflationQueue<K,V> implements ExchangeConflationQueue<K,V> {
051
052    private final Queue<Entry<K,MarkedValue<V>>> queue;
053    private final Map<K,Entry<K,MarkedValue<V>>> entryMap;
054    private final Merger<? super K, V> merger;
055
056    private final ThreadLocal<Appender<K,V>> appender = ThreadLocal.withInitial(MergeQueueAppender::new);
057    private final ThreadLocal<ExchangePoller<K,V>> poller = ThreadLocal.withInitial(MergeQueuePoller::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 MergeConflationQueue(final Queue<Entry<K,MarkedValue<V>>> queue,
063                                 final Map<K,Entry<K,MarkedValue<V>>> entryMap,
064                                 final Merger<? super K, V> merger,
065                                 final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier,
066                                 final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) {
067        this.queue = Objects.requireNonNull(queue);
068        this.entryMap = Objects.requireNonNull(entryMap);
069        this.merger = Objects.requireNonNull(merger);
070        this.appenderListenerSupplier = Objects.requireNonNull(appenderListenerSupplier);
071        this.pollerListenerSupplier = Objects.requireNonNull(pollerListenerSupplier);
072    }
073
074    /**
075     * Constructor with queue factory and merger.  A concurrent hash map is used to manage entries per conflation
076     * key.
077     *
078     * @param queueFactory the factory to create the backing queue
079     * @param merger the merge strategy to use if conflation occurs
080     */
081    public MergeConflationQueue(final Supplier<? extends Queue<Object>> queueFactory,
082                                final Merger<? super K, V> merger) {
083        this(queueFactory, merger, () -> AppenderListener.NOOP, () -> PollerListener.NOOP);
084    }
085
086    /**
087     * Constructor with queue factory and merger.  A concurrent hash map is used to manage entries per conflation
088     * key.
089     *
090     * @param queueFactory  the factory to create the backing queue
091     * @param merger        the merge strategy to use if conflation occurs
092     * @param appenderListenerSupplier  a supplier for a listener to monitor the enqueue operations
093     * @param pollerListenerSupplier    a supplier for a listener to monitor the poll operations
094     */
095    public MergeConflationQueue(final Supplier<? extends Queue<Object>> queueFactory,
096                                final Merger<? super K, V> merger,
097                                final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier,
098                                final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) {
099        this(queueFactory, ConcurrentHashMap::new, merger, appenderListenerSupplier, pollerListenerSupplier);
100    }
101
102    /**
103     * Constructor with queue factory, entry map factory and merger.
104     *
105     * @param queueFactory      the factory to create the backing queue
106     * @param entryMapFactory   the factory to create the map that manages entries per conflation key
107     * @param merger            the merge strategy to use if conflation occurs
108     * @param appenderListenerSupplier  a supplier for a listener to monitor the enqueue operations
109     * @param pollerListenerSupplier    a supplier for a listener to monitor the poll operations
110     */
111    public MergeConflationQueue(final Supplier<? extends Queue<Object>> queueFactory,
112                                final Supplier<? extends Map<Object, Object>> entryMapFactory,
113                                final Merger<? super K, V> merger,
114                                final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier,
115                                final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) {
116        this(Factories.createQueue(queueFactory), Factories.createMap(entryMapFactory), merger,
117                appenderListenerSupplier, pollerListenerSupplier);
118    }
119
120    /**
121     * Constructor with queue factory, merger and the exhaustive list of conflation keys.  A hash map is pre-initialized
122     * with all the conflation keys and pre-allocated entries.
123     *
124     * @param queueFactory the factory to create the backing queue
125     * @param merger the merge strategy to use if conflation occurs
126     * @param allConflationKeys all conflation keys that will ever be used with this conflation queue instance
127     */
128    public MergeConflationQueue(final Supplier<? extends Queue<Object>> queueFactory,
129                                final Merger<? super K, V> merger,
130                                final List<? extends K> allConflationKeys) {
131        this(queueFactory, merger, allConflationKeys, () -> AppenderListener.NOOP, () -> PollerListener.NOOP);
132    }
133
134    /**
135     * Constructor with queue factory, merger and the exhaustive list of conflation keys.  A hash map is pre-initialized
136     * with all the conflation keys and pre-allocated entries.
137     *
138     * @param queueFactory the factory to create the backing queue
139     * @param merger the merge strategy to use if conflation occurs
140     * @param allConflationKeys all conflation keys that will ever be used with this conflation queue instance
141     * @param appenderListenerSupplier a supplier for a listener to monitor the enqueue operations
142     * @param pollerListenerSupplier a supplier for a listener to monitor the poll operations
143     */
144    public MergeConflationQueue(final Supplier<? extends Queue<Object>> queueFactory,
145                                final Merger<? super K, V> merger,
146                                final List<? extends K> allConflationKeys,
147                                final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier,
148                                final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) {
149        this(Factories.createQueue(queueFactory), Entry.eagerlyInitialiseEntryMap(allConflationKeys, MarkedValue::new),
150                merger, appenderListenerSupplier, pollerListenerSupplier);
151    }
152
153    /**
154     * Static constructor method for a conflation queue with queue factory, merger and the conflation key enum class.
155     * An enum map is pre-initialized with all the conflation keys and pre-allocated entries.
156     *
157     * @param queueFactory the factory to create the backing queue
158     * @param merger the merge strategy to use if conflation occurs
159     * @param conflationKeyClass the conflation key enum class
160     * @param <K> the type of the conflation key
161     * @param <V> the type of elements in the queue
162     * @return the new conflation queue instance
163     */
164    public static <K extends Enum<K>,V> MergeConflationQueue<K,V> forEnumConflationKey(final Supplier<? extends Queue<Object>> queueFactory,
165                                                                                       final Merger<? super K, V> merger,
166                                                                                       final Class<K> conflationKeyClass) {
167        return forEnumConflationKey(queueFactory, merger, conflationKeyClass, () -> AppenderListener.NOOP, () -> PollerListener.NOOP);
168    }
169
170    /**
171     * Static constructor method for a conflation queue with queue factory, merger and the conflation key enum class.
172     * An enum map is pre-initialized with all the conflation keys and pre-allocated entries.
173     *
174     * @param queueFactory the factory to create the backing queue
175     * @param merger the merge strategy to use if conflation occurs
176     * @param conflationKeyClass the conflation key enum class
177     * @param appenderListenerSupplier a supplier for a listener to monitor the enqueue operations
178     * @param pollerListenerSupplier a supplier for a listener to monitor the poll operations
179     * @param <K> the type of the conflation key
180     * @param <V> the type of elements in the queue
181     * @return the new conflation queue instance
182     */
183    public static <K extends Enum<K>,V> MergeConflationQueue<K,V> forEnumConflationKey(final Supplier<? extends Queue<Object>> queueFactory,
184                                                                                       final Merger<? super K, V> merger,
185                                                                                       final Class<K> conflationKeyClass,
186                                                                                       final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier,
187                                                                                       final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) {
188        return new MergeConflationQueue<>(
189                Factories.createQueue(queueFactory), Entry.eagerlyInitialiseEntryEnumMap(conflationKeyClass, MarkedValue::new),
190                merger, appenderListenerSupplier, pollerListenerSupplier
191        );
192    }
193
194    @Override
195    public Appender<K, V> appender() {
196        return appender.get();
197    }
198
199    @Override
200    public ExchangePoller<K, V> poller() {
201        return poller.get();
202    }
203
204    @Override
205    public int size() {
206        return queue.size();
207    }
208
209    @Contended
210    private final static class MarkedValue<V> {
211        enum State {UNCONFIRMED, CONFIRMED, UNUSED}
212        V value;
213        volatile State state = State.UNUSED;
214        MarkedValue<V> initializeWithUnconfirmed(final V value) {
215            this.value = Objects.requireNonNull(value);
216            this.state = State.UNCONFIRMED;
217            return this;
218        }
219        MarkedValue<V> initalizeWithUnused(final V value) {
220            this.value = value;//nulls allowed here
221            this.state = State.UNUSED;
222            return this;
223        }
224        void confirm() {
225            this.state = State.CONFIRMED;
226        }
227
228        void confirmWith(final V value) {
229            this.value = value;
230            this.state = State.CONFIRMED;
231        }
232
233        V awaitAndRelease() {
234            awaitFinalState();
235            return release();
236        }
237
238        V release() {
239            final V released = value;
240            state = State.UNUSED;
241            this.value = null;
242            return released;
243        }
244
245        boolean isUnused() {
246            return awaitFinalState() == State.UNUSED;
247        }
248
249        private State awaitFinalState() {
250            State s;
251            do {
252                s = state;
253            } while (s == State.UNCONFIRMED);
254            return s;
255        }
256    }
257
258    private final class MergeQueueAppender implements Appender<K,V> {
259        final AppenderListener<? super K, ? super V> appenderListener = appenderListenerSupplier.get();
260        @Contended
261        MarkedValue<V> markedValue = new MarkedValue<>();
262        @Override
263        public V enqueue(final K conflationKey, final V value) {
264            Objects.requireNonNull(value);
265            final Entry<K,MarkedValue<V>> entry = entryMap.computeIfAbsent(conflationKey, k -> new Entry<>(k, new MarkedValue<>()));
266            final MarkedValue<V> newValue = markedValue.initializeWithUnconfirmed(value);
267            final MarkedValue<V> oldValue = entry.value.getAndSet(newValue);
268            final V add;
269            final V old;
270            final AppenderListener.Conflation conflation;
271            try {
272                if (oldValue.isUnused()) {
273                    old = oldValue.release();
274                    add = value;
275                    newValue.confirm();
276                    queue.add(entry);
277                    conflation = AppenderListener.Conflation.UNCONFLATED;
278                } else {
279                    old = oldValue.awaitAndRelease();
280                    try {
281                        add = merger.merge(conflationKey, old, value);
282                    } catch (final Throwable t) {
283                        newValue.confirmWith(old);
284                        throw t;
285                    }
286                    newValue.confirmWith(add);
287                    conflation = AppenderListener.Conflation.MERGED;
288                }
289            } finally {
290                markedValue = oldValue;
291            }
292            //NOTE: ok if below listener throws exception now as it cannot messs with the queue's state
293            appenderListener.enqueued(MergeConflationQueue.this, conflationKey, add, old, conflation);
294            return old;
295        }
296    }
297
298    private final class MergeQueuePoller implements ExchangePoller<K,V> {
299        final PollerListener<? super K, ? super V> pollerListener = pollerListenerSupplier.get();
300        @Contended
301        MarkedValue<V> markedValue = new MarkedValue<>();
302        @Override
303        public V poll(final BiConsumer<? super K, ? super V> consumer, final V exchange) {
304            final Entry<K,MarkedValue<V>> entry = queue.poll();
305            if (entry != null) {
306                final MarkedValue<V> exchangeValue = markedValue.initalizeWithUnused(exchange);
307                final MarkedValue<V> polledValue = entry.value.getAndSet(exchangeValue);
308                final V value = polledValue.awaitAndRelease();
309                markedValue = polledValue;
310                consumer.accept(entry.key, value);
311                pollerListener.polled(MergeConflationQueue.this, entry.key, value);
312                return value;
313            } else {
314                pollerListener.polledButFoundEmpty(MergeConflationQueue.this);
315                return null;
316            }
317        }
318    }
319}