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}