/* * Copyright (C) 2006-2016 DLR, Germany * * All rights reserved * * http://www.rcenvironment.de/ */ package de.rcenvironment.toolkit.modules.concurrency.internal; import java.util.ArrayList; import java.util.List; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncCallbackExceptionPolicy; import de.rcenvironment.toolkit.modules.concurrency.api.AsyncOrderedExecutionQueue; import de.rcenvironment.toolkit.modules.concurrency.api.BatchAggregator; import de.rcenvironment.toolkit.modules.concurrency.api.BatchProcessor; import de.rcenvironment.toolkit.modules.concurrency.api.ConcurrencyUtilsFactory; import de.rcenvironment.toolkit.modules.concurrency.api.TaskDescription; /** * A generic class to group sequentially-generated elements into ordered batches. A batch is returned (to a given {@link BatchProcessor}) * when a maximum number of elements is reached, or after a specified time has elapsed since the batch was created. Batches are created * implicitly when an element is added while no batch is already active. * * @param <T> the element type to aggregate * * @author Robert Mischke */ public class BatchAggregatorImpl<T> implements BatchAggregator<T> { /** * A Runnable to check for maximum latency timeouts. * * @author Robert Mischke */ private final class MaxLatencyTimerCallback implements Runnable { private List<T> relevantBatch; MaxLatencyTimerCallback(List<T> relevantBatch) { this.relevantBatch = relevantBatch; } @Override @TaskDescription("BatchAggregator Max Latency Timer") public void run() { // delegate for proper synchronization onMaxLatencyTimerCallback(relevantBatch); } } /** * A Runnable to dispatch a completed batch to the configured {@link BatchProcessor}. * * @param <T> the element type of the batch that should be dispatched; * * @author Robert Mischke */ // TODO this class was static before making statisticsTrackerService a field; review private final class DispatchRunnable<T> implements Runnable { private static final String ASYNC_TASK_DESCRIPTION = "BatchAggregator dispatch"; private final List<T> detachedBatchReference; private final BatchProcessor<T> processor; private DispatchRunnable(List<T> detachedBatchReference, BatchProcessor<T> processor) { this.detachedBatchReference = detachedBatchReference; this.processor = processor; } @Override @TaskDescription(ASYNC_TASK_DESCRIPTION) public void run() { try { processor.processBatch(detachedBatchReference); } catch (RuntimeException e) { // the best we can do here is log the error and discard the batch // synchronized so that the proper logger is always "seen" synchronized (BatchAggregatorImpl.class) { log.error("Uncaught exception in batch processor " + processor, e); } throw e; // allow then async queue to react on the exception as well } // TODO improve fetching internalServicesHolder.getStatisticsTrackerService() .getCounterCategory(AsyncOrderedExecutionQueue.STATS_COUNTER_SHARED_CATEGORY_NAME).count( ASYNC_TASK_DESCRIPTION); } } // not made final as it needs to be replaced from a unit test private static Log log = LogFactory.getLog(BatchAggregatorImpl.class); private List<T> currentBatch; // private Deque<List<T>> outputQueue = new LinkedList<List<T>>(); private final int maxBatchSize; private final long maxLatency; private final BatchProcessor<T> processor; private final AsyncOrderedExecutionQueue dispatchQueue; private final ConcurrencyUtilsServiceHolder internalServicesHolder; private final ConcurrencyUtilsFactory concurrencyUtilsFactory; public BatchAggregatorImpl(int maxBatchSize, long maxLatency, BatchProcessor<T> processor, ConcurrencyUtilsServiceHolder internalServicesHolder) { this.maxBatchSize = maxBatchSize; this.maxLatency = maxLatency; this.processor = processor; this.internalServicesHolder = internalServicesHolder; this.concurrencyUtilsFactory = internalServicesHolder.getConcurrencyUtilsFactory(); // TODO @5.0: policy chosen for backwards compatibility; review/make configurable? this.dispatchQueue = concurrencyUtilsFactory.createAsyncOrderedExecutionQueue( AsyncCallbackExceptionPolicy.LOG_AND_PROCEED); } /** * Adds an element for aggregation. May trigger the internal creation of a new batch or the sending of a finished batch when the the * maximum size limit is reached. * * @param element the element to add */ @Override public synchronized void enqueue(T element) { if (currentBatch == null) { startNewBatch(); } currentBatch.add(element); int size = currentBatch.size(); if (size >= maxBatchSize) { // sanity check if (size > maxBatchSize) { throw new IllegalArgumentException("maxBatchSize exceeded?"); } // send current batch; the next incoming element will start a new one endCurrentBatchAndEnqueueForProcessing(); } } /** * Logger access for unit tests. */ protected static synchronized void setLogger(Log newLog) { BatchAggregatorImpl.log = newLog; } /** * Logger access for unit tests. */ protected static synchronized Log getLogger() { return log; } private synchronized void onMaxLatencyTimerCallback(List<T> relevantBatch) { if (currentBatch != relevantBatch) { // in this case, the batch associated with the calling TimerTask // was already sent out because max size was reached return; } endCurrentBatchAndEnqueueForProcessing(); } private void startNewBatch() { currentBatch = new ArrayList<T>(); internalServicesHolder.getAsyncTaskService().scheduleAfterDelay(new MaxLatencyTimerCallback(currentBatch), maxLatency); } private void endCurrentBatchAndEnqueueForProcessing() { // note: it is important that this Runnable copies the current reference, and does not accidentally use the field! - misc_ro dispatchQueue.enqueue(new DispatchRunnable<T>(currentBatch, processor)); currentBatch = null; } }