Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
package com.netflix.eventbus.impl;
import com.google.common.annotations.VisibleForTesting;
import com.netflix.eventbus.spi.Subscribe;
import com.netflix.eventbus.spi.SubscriberConfigProvider;
import com.netflix.eventbus.utils.EventBusUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nullable;
import java.lang.reflect.Method;
import java.util.Iterator;
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock;
/**
* Implementation of {@link Subscribe.BatchingStrategy#Age} for {@link EventBusImpl}. The following is the strategy and
* nuances of this implementation:
*
*
This queue maintains a current batch, an instance of {@link AgeBatch}
*
All calls to {@link AgeBatchingQueue#offer(Object)} will add the event to this batch.
*
All batches which are aged (crossed the max age) move to a blocking queue.
*
All age based batching subscribers share a single {@link Timer} to deduce the batch age periodically.
*
All individual instances of this queue will schedule a single task in the above timer to deduce the batch age
* according to the batch age specified in {@link Subscribe}
*
The above task will periodically move the current batch to the old batches queue, mentioned above.
*
In case, the old batch queue is full, the reaper task sets a flag signifying that the queue is full and does
* NOT reap the current batch.
*
Every subsequent offer to this queue, will try to reap the current batch, failing which, the offer will fail.
*
The failure of above offer will typically make the consumer remove & discard a batch and retry.
* @author Nitesh Kant ([email protected])
*/
class AgeBatchingQueue implements EventBusImpl.ConsumerQueueSupplier.ConsumerQueue {
protected static final Logger LOGGER = LoggerFactory.getLogger(AgeBatchingQueue.class);
protected AtomicReference currentBatch;
protected LinkedBlockingQueue oldBatches;
protected AtomicBoolean oldBatchesQueueFull;
protected ReentrantLock batchReapingLock;
/**
* This IS a static timer. This is solely used for the purpose of routinely reaping the current batch to the old
* batches queue. The tasks will ALWAYS use offer on the old batches queue and if it can not enqueue will leave the
* current batch as is. After that any subsequent offer will first offer the current batch to the old queue, which if
* fails, will fail the offer. So, in a nutshell, these timer tasks must be super quick and never block. So, it is
* fine to even schedule thousands of these task (i.e. thousands of aged/size & age consumers) to this timer.
*/
protected static Timer batchAgeChecker = new Timer("eventbus-consumer-current-batch-reaper", true);
protected final String subscriberName;
protected TimerTask reaper;
protected Subscribe.BatchingStrategy batchingStrategy;
protected AtomicLong queueSizeCounter;
AgeBatchingQueue(Method subscriber, SubscriberConfigProvider.SubscriberConfig subscribe, AtomicLong queueSizeCounter) {
this(subscriber, subscribe, true, queueSizeCounter);
}
@VisibleForTesting
AgeBatchingQueue(Method subscriber, SubscriberConfigProvider.SubscriberConfig subscribe, boolean scheduleReaper,
AtomicLong queueSizeCounter) {
this.queueSizeCounter = queueSizeCounter;
subscriberName = subscriber.toGenericString();
batchingStrategy = subscribe.getBatchingStrategy();
oldBatches = new LinkedBlockingQueue(EventBusUtils.getQueueSize(subscribe));
currentBatch = new AtomicReference(createNewBatch(subscribe));
oldBatchesQueueFull = new AtomicBoolean();
batchReapingLock = new ReentrantLock();
int batchAge = subscribe.getBatchAge();
reaper = new ReaperTask();
// For testing we do not schedule a reaper but invoke reaping at will to have more predictability.
if (scheduleReaper) {
batchAgeChecker.schedule(reaper, batchAge, batchAge);
}
}
@Override
public boolean offer(Object event) {
if (oldBatchesQueueFull.get()) {
if (!reapCurrentBatch("Offering Thread")) {
return false;
}
}
return currentBatch.get().addEvent(event);
}
@Override
public Object nonBlockingTake() {
AgeBatch batch = oldBatches.poll();
if (null != batch) {
queueSizeCounter.decrementAndGet();
}
return batch;
}
@Override
public Object blockingTake() throws InterruptedException {
AgeBatch batch = oldBatches.take();
queueSizeCounter.decrementAndGet();
return batch;
}
@Override
public void clear() {
oldBatches.clear();
currentBatch.get().clear();
queueSizeCounter.set(0);
}
@VisibleForTesting
AgeBatch getCurrentBatch() {
return currentBatch.get();
}
@VisibleForTesting
AgeBatch blockingTakeWithTimeout(long timeoutInMillis) throws InterruptedException {
return oldBatches.poll(timeoutInMillis, TimeUnit.MILLISECONDS);
}
@VisibleForTesting
boolean invokeReaping() {
return reapCurrentBatch("Test driven explicit reaping");
}
protected boolean reapCurrentBatch(String operatorName) {
AgeBatch currentBatchRef = currentBatch.get();
if (currentBatchRef.events.isEmpty()) {
return true;
}
// We should not block here as the offer & reaper thread both does not block in any condition.
if (batchReapingLock.tryLock()) {
try {
if (oldBatches.offer(currentBatchRef)) {
currentBatch.getAndSet(createNewBatch(null));
queueSizeCounter.incrementAndGet();
LOGGER.debug(String.format(
"[Reaping source: %s , Batching strategy: %s ] Reaped the old batch with size %s for subscriber: %s",
operatorName, batchingStrategy, currentBatchRef.events.size(), subscriberName));
oldBatchesQueueFull.set(false);
return true;
} else {
oldBatchesQueueFull.set(true);
LOGGER.info(String.format(
"[Reaping source: %s , Batching strategy: %s ] Old batches queue for subscriber %s is full. Not reaping the batch till we get space.",
operatorName, batchingStrategy, subscriberName));
}
} finally {
batchReapingLock.unlock();
}
} else {
LOGGER.debug(String.format(
"[Reaping source: %s , Batching strategy: %s ] Subscriber: %s did not reap as there is another thread already reaping.",
operatorName, batchingStrategy, subscriberName));
}
return false;
}
protected AgeBatch createNewBatch(@Nullable SubscriberConfigProvider.SubscriberConfig subscribe) {
return new AgeBatch();
}
/**
* @author Nitesh Kant ([email protected])
*/
protected class AgeBatch implements EventBatch {
@VisibleForTesting
ConcurrentLinkedQueue events;
protected AgeBatch() {
events = new ConcurrentLinkedQueue();
}
@SuppressWarnings("unchecked")
protected boolean addEvent(Object event) {
return events.add(event);
}
@Override
public Iterator iterator() {
return events.iterator(); // This will happen only after we enqueue this batch to the oldBatches queue.
// So, no mutations will happen to this events list after that and hence we can
// not loose events that are added here but not reflecting in the iterator.
}
protected void clear() {
events.clear();
}
}
private class ReaperTask extends TimerTask {
@Override
public void run() {
try {
reapCurrentBatch("Reaper");
} catch (Throwable th) {
LOGGER.error(String.format(
"Reaper thread for subscriber: %s threw an error while reaping. Eating exception.",
subscriberName), th);
}
}
}
}