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