package org.infinispan.notifications.cachelistener;
import java.util.Queue;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicReferenceArray;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.infinispan.container.InternalEntryFactory;
import org.infinispan.container.entries.CacheEntry;
import org.infinispan.distribution.DistributionManager;
import org.infinispan.notifications.cachelistener.event.CacheEntryEvent;
import org.infinispan.notifications.cachelistener.event.Event;
import org.infinispan.notifications.impl.ListenerInvocation;
import org.infinispan.util.KeyValuePair;
/**
* This handler is to be used with a clustered distributed cache. This handler does special optimizations to
* alllow for queueing to occur per segment. This way we don't retain all new events in memory unlike
* {@link QueueingAllSegmentListener} until the iteration is complete.
*
* @author wburns
* @since 7.0
*/
class DistributedQueueingSegmentListener<K, V> extends BaseQueueingSegmentListener<K, V, CacheEntryEvent<K, V>> {
private final AtomicReferenceArray<Queue<KeyValuePair<CacheEntryEvent<K, V>, ListenerInvocation<Event<K, V>>>>> queues;
private final DistributionManager distributionManager;
protected final InternalEntryFactory entryFactory;
private Stream<Integer> justCompletedSegments = Stream.empty();
private final Consumer<Integer> completeSegment = this::completeSegment;
public DistributedQueueingSegmentListener(InternalEntryFactory entryFactory, DistributionManager distributionManager) {
this.entryFactory = entryFactory;
this.distributionManager = distributionManager;
// we assume the # of segments won't change between different consistent hashes
this.queues = new AtomicReferenceArray<>(distributionManager.getReadConsistentHash().getNumSegments());
for (int i = 0; i < queues.length(); ++i) {
Queue<KeyValuePair<CacheEntryEvent<K, V>, ListenerInvocation<Event<K, V>>>> queue = new ConcurrentLinkedQueue<>();
queues.set(i, queue);
}
}
@Override
public boolean handleEvent(EventWrapper<K, V, CacheEntryEvent<K, V>> wrapped, ListenerInvocation<Event<K, V>> invocation) {
K key = wrapped.getKey();
// If we already completed, don't enqueue
boolean enqueued = !completed.get();
CacheEntryEvent<K, V> event = wrapped.getEvent();
CacheEntry<K, V> cacheEntry = entryFactory.create(event.getKey(), event.getValue(), event.getMetadata());
if (enqueued && !addEvent(key, cacheEntry.getValue() != null ? cacheEntry : REMOVED)) {
// If it wasn't added it means we haven't processed this value yet, so add it to the queue for this segment
int segment = distributionManager.getReadConsistentHash().getSegment(key);
Queue<KeyValuePair<CacheEntryEvent<K, V>, ListenerInvocation<Event<K, V>>>> queue;
// If the queue is not null, try to see if we can add to it
if ((queue = queues.get(segment)) != null) {
KeyValuePair<CacheEntryEvent<K, V>, ListenerInvocation<Event<K, V>>> eventPair =
new KeyValuePair<>(event, invocation);
queue.add(eventPair);
// If the queue was removed, that means we had a concurrent completion, so we need to verify if we
// have to run the event manually
if (queues.get(segment) == null) {
if (queue.remove(eventPair)) {
enqueued = false;
}
}
} else {
// if the queue is already null that means it was transferred so just raise the notification
enqueued = false;
}
}
return enqueued;
}
@Override
public void transferComplete() {
justCompletedSegments.forEach(completeSegment);
completed.set(true);
notifiedKeys.clear();
for (int i = 0; i < queues.length(); ++i) {
queues.set(i, null);
}
}
public Object markKeyAsProcessing(K key) {
// By putting the NOTIFIED value it has signaled that any more updates for this key have to be enqueud instead
// of taking the last one
return notifiedKeys.put(key, NOTIFIED);
}
@Override
public void notifiedKey(K key) {
// This relies on the fact that notifiedKey is immediately called after the entry has finished being iterated on
justCompletedSegments.forEach(completeSegment);
justCompletedSegments = Stream.empty();
}
private void completeSegment(int segment) {
Queue<KeyValuePair<CacheEntryEvent<K, V>, ListenerInvocation<Event<K, V>>>> queue = queues.get(segment);
if (queue != null) {
if (trace) {
log.tracef("Completed segment %s", segment);
}
for (KeyValuePair<CacheEntryEvent<K, V>, ListenerInvocation<Event<K, V>>> event : queue) {
// The InitialTransferInvocation already did the converter if needed
event.getValue().invoke(event.getKey());
}
queues.set(segment, null);
}
}
public void segmentCompleted(Set<Integer> segments) {
justCompletedSegments = segments.stream().filter(s -> queues.get(s) != null);
}
}