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}