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 Map<K,Entry<K,MarkedValue<V>>> entryMap; 054 private final Queue<Entry<K,MarkedValue<V>>> queue; 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 @SuppressWarnings("unchecked")//casting a queue that takes objects to one that takes Entry is fine as long as we only add Entry objects 063 private EvictConflationQueue(final Map<K,Entry<K,MarkedValue<V>>> entryMap, 064 final Supplier<? extends Queue<Object>> queueFactory, 065 final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier, 066 final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) { 067 this(entryMap, (Queue<Entry<K,MarkedValue<V>>>)(Object)queueFactory.get(), appenderListenerSupplier, pollerListenerSupplier); 068 } 069 070 private EvictConflationQueue(final Map<K,Entry<K,MarkedValue<V>>> entryMap, 071 final Queue<Entry<K,MarkedValue<V>>> queue, 072 final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier, 073 final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) { 074 this.entryMap = Objects.requireNonNull(entryMap); 075 this.queue = Objects.requireNonNull(queue); 076 this.appenderListenerSupplier = Objects.requireNonNull(appenderListenerSupplier); 077 this.pollerListenerSupplier = Objects.requireNonNull(pollerListenerSupplier); 078 } 079 080 /** 081 * Constructor with queue factory. A concurrent hash map is used to to recycle entries per conflation key. 082 * 083 * @param queueFactory the factory to create the backing queue 084 */ 085 public EvictConflationQueue(final Supplier<? extends Queue<Object>> queueFactory) { 086 this(queueFactory, () -> AppenderListener.NOOP, () -> PollerListener.NOOP); 087 } 088 089 /** 090 * Constructor with queue factory. A concurrent hash map is used to to recycle entries per conflation key. 091 * 092 * @param queueFactory the factory to create the backing queue 093 * @param appenderListenerSupplier a supplier for a listener to monitor the enqueue operations 094 * @param pollerListenerSupplier a supplier for a listener to monitor the poll operations 095 */ 096 public EvictConflationQueue(final Supplier<? extends Queue<Object>> queueFactory, 097 final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier, 098 final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) { 099 this(new ConcurrentHashMap<>(), queueFactory, appenderListenerSupplier, pollerListenerSupplier); 100 } 101 102 /** 103 * Constructor with queue factory and the exhaustive list of conflation keys. A hash map is pre-initialized with 104 * all the conflation keys and pre-allocated entries. 105 * 106 * @param queueFactory the factory to create the backing queue 107 * @param allConflationKeys all conflation keys that will ever be used with this conflation queue instance 108 */ 109 public EvictConflationQueue(final Supplier<? extends Queue<Object>> queueFactory, 110 final List<? extends K> allConflationKeys) { 111 this(queueFactory, allConflationKeys, () -> AppenderListener.NOOP, () -> PollerListener.NOOP); 112 } 113 114 /** 115 * Constructor with queue factory and the exhaustive list of conflation keys. A hash map is pre-initialized with 116 * all the conflation keys and pre-allocated entries. 117 * 118 * @param queueFactory the factory to create the backing queue 119 * @param allConflationKeys all conflation keys that will ever be used with this conflation queue instance 120 * @param appenderListenerSupplier a supplier for a listener to monitor the enqueue operations 121 * @param pollerListenerSupplier a supplier for a listener to monitor the poll operations 122 */ 123 public EvictConflationQueue(final Supplier<? extends Queue<Object>> queueFactory, 124 final List<? extends K> allConflationKeys, 125 final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier, 126 final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) { 127 this(Entry.eagerlyInitialiseEntryMap(allConflationKeys, MarkedValue::new), queueFactory, appenderListenerSupplier, pollerListenerSupplier); 128 } 129 130 /** 131 * Static constructor method for a conflation queue with queue factory and the conflation key enum class. An enum 132 * map is pre-initialized with all the conflation keys and pre-allocated entries. 133 * 134 * @param queueFactory the factory to create the backing queue 135 * @param conflationKeyClass the conflation key enum class 136 * @param <K> the type of the conflation key 137 * @param <V> the type of elements in the queue 138 * @return the new conflation queue instance 139 */ 140 public static <K extends Enum<K>,V> EvictConflationQueue<K,V> forEnumConflationKey(final Supplier<? extends Queue<Object>> queueFactory, 141 final Class<K> conflationKeyClass) { 142 return forEnumConflationKey(queueFactory, conflationKeyClass, () -> AppenderListener.NOOP, () -> PollerListener.NOOP); 143 } 144 145 /** 146 * Static constructor method for a conflation queue with queue factory and the conflation key enum class. An enum 147 * map is pre-initialized with all the conflation keys and pre-allocated entries. 148 * 149 * @param queueFactory the factory to create the backing queue 150 * @param conflationKeyClass the conflation key enum class 151 * @param appenderListenerSupplier a supplier for a listener to monitor the enqueue operations 152 * @param pollerListenerSupplier a supplier for a listener to monitor the poll operations 153 * @param <K> the type of the conflation key 154 * @param <V> the type of elements in the queue 155 * @return the new conflation queue instance 156 */ 157 public static <K extends Enum<K>,V> EvictConflationQueue<K,V> forEnumConflationKey(final Supplier<? extends Queue<Object>> queueFactory, 158 final Class<K> conflationKeyClass, 159 final Supplier<? extends AppenderListener<? super K, ? super V>> appenderListenerSupplier, 160 final Supplier<? extends PollerListener<? super K, ? super V>> pollerListenerSupplier) { 161 return new EvictConflationQueue<>( 162 Entry.eagerlyInitialiseEntryEnumMap(conflationKeyClass, MarkedValue::new), queueFactory, 163 appenderListenerSupplier, pollerListenerSupplier 164 ); 165 } 166 167 @Override 168 public Appender<K, V> appender() { 169 return appender.get(); 170 } 171 172 @Override 173 public ExchangePoller<K, V> poller() { 174 return poller.get(); 175 } 176 177 @Override 178 public int size() { 179 return queue.size(); 180 } 181 182 @Contended 183 private final static class MarkedValue<V> { 184 enum State {USED, UNUSED} 185 V value; 186 State state = State.UNUSED; 187 MarkedValue<V> initializeWithUsed(final V value) { 188 this.value = Objects.requireNonNull(value); 189 this.state = State.USED; 190 return this; 191 } 192 MarkedValue<V> initalizeWithUnused(final V value) { 193 this.value = value;//nulls allowed here 194 this.state = State.UNUSED; 195 return this; 196 } 197 198 V markUnusedAndRelease() { 199 final V released = value; 200 state = State.UNUSED; 201 value = null; 202 return released; 203 } 204 205 boolean isUnused() { 206 return state == State.UNUSED; 207 } 208 } 209 210 private final class EvictQueueAppender implements Appender<K,V> { 211 final AppenderListener<? super K, ? super V> appenderListener = appenderListenerSupplier.get(); 212 @Contended 213 MarkedValue<V> markedValue = new MarkedValue<>(); 214 @Override 215 public V enqueue(final K conflationKey, final V value) { 216 Objects.requireNonNull(value); 217 final Entry<K, MarkedValue<V>> entry = entryMap.computeIfAbsent(conflationKey, k -> new Entry<>(k, new MarkedValue<>())); 218 final MarkedValue<V> newValue = markedValue.initializeWithUsed(value); 219 final MarkedValue<V> oldValue = entry.value.getAndSet(newValue); 220 final AppenderListener.Conflation conflation; 221 if (oldValue.isUnused()) { 222 queue.add(entry); 223 conflation = AppenderListener.Conflation.UNCONFLATED; 224 } else { 225 conflation = AppenderListener.Conflation.EVICTED; 226 } 227 markedValue = oldValue; 228 final V old = oldValue.markUnusedAndRelease(); 229 //NOTE: ok if listener throws exception now as it cannot mess up this queue's state 230 appenderListener.enqueued(EvictConflationQueue.this, conflationKey, value, old, conflation); 231 return old; 232 } 233 } 234 235 private final class EvictQueuePoller implements ExchangePoller<K,V> { 236 final PollerListener<? super K, ? super V> pollerListener = pollerListenerSupplier.get(); 237 @Contended 238 MarkedValue<V> markedValue = new MarkedValue<>(); 239 @Override 240 public V poll(final BiConsumer<? super K, ? super V> consumer, final V exchange) { 241 final Entry<K,MarkedValue<V>> entry = queue.poll(); 242 if (entry != null) { 243 final MarkedValue<V> exchangeValue = markedValue.initalizeWithUnused(exchange); 244 final MarkedValue<V> polledValue = entry.value.getAndSet(exchangeValue); 245 final V value = polledValue.markUnusedAndRelease(); 246 markedValue = polledValue; 247 consumer.accept(entry.key, value); 248 pollerListener.polled(EvictConflationQueue.this, entry.key, value); 249 return value; 250 } else { 251 pollerListener.polledButFoundEmpty(EvictConflationQueue.this); 252 return null; 253 } 254 } 255 } 256}