package org.rhq.server.metrics.aggregation; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; import org.rhq.server.metrics.AbortedException; /** * <p> * This class is a synchronization mechanism for producers and consumers. CountDownLatch and Semaphore are commonly * used to provide synchronization for producers and consumers, TaskTracker adds some functionality that they lack. * With CountDownLatch you need to know the number of events or tasks up front. TaskTracker is intended to be used * where the total number of tasks is not known up front. Semaphore provides a number of permits that can be checked out * and then checked back in. The problem is that all permits being checked in does not necessarily mean that all tasks * are finished as would be the case if the producer is still scheduling tasks. TaskTracker handles that scenario. * </p> * <p> * It should also be noted that this class is design for use with a single producer and multiple consumers. * </p> * * @author John Sanda */ class TaskTracker { private volatile int remainingTasks; private volatile boolean schedulingFinished; private ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private CountDownLatch allTasksFinished = new CountDownLatch(1); private volatile boolean aborted; private String errorMessage; /** * Increases the count of remaining tasks. */ public void addTask() { try { lock.writeLock().lock(); remainingTasks++; } finally { lock.writeLock().unlock(); } } /** * This method is intended primarily for debugging purposes to log the progress. While other methods in this class * obtain a read or write lock, this method intentionally does not. There is no need to impose the locking overhead * since this method only reads a single variable that is a volatile. * * @return The number of remaining or outstanding tasks to be completed */ public int getRemainingTasks() { return remainingTasks; } /** * Should be called by the producer when it has finished scheduling tasks. Moreover the producer must invoke this * method before it invokes {@link #waitForTasksToFinish()}. Failure to do so will cause the producer to block * indefinitely. */ public void finishedSchedulingTasks() { try { lock.writeLock().lock(); schedulingFinished = true; } finally { lock.writeLock().unlock(); } } /** * Should be invoked by a consumer when it completes a task. */ public void finishedTask() { try { lock.writeLock().lock(); remainingTasks--; if (schedulingFinished && remainingTasks == 0) { allTasksFinished.countDown(); } } finally { lock.writeLock().unlock(); } } /** * Should be invoked by the producer <strong>only</strong> after it has invoked {@link #finishedSchedulingTasks()}. * If this method gets invoked first, the producer will block indefinitely. This method will block until all tasks * have completed. If all tasks have already completed, this method returns immediately. * * @throws InterruptedException If the producer thread is interrupted while waiting * @throws AbortedException If task processing has been abort which is accomplished by calling * {@link #abort(String)} */ public void waitForTasksToFinish() throws InterruptedException, AbortedException { try { lock.readLock().lock(); if (aborted) { throw new AbortedException(errorMessage); } if (remainingTasks == 0) { return; } } finally { lock.readLock().unlock(); } allTasksFinished.await(); try { lock.readLock().lock(); if (aborted) { throw new AbortedException(errorMessage); } } finally { lock.readLock().unlock(); } } /** * Should be invoked by the producer <strong>only</strong> after it has invoked {@link #finishedSchedulingTasks()}. * If this method gets invoked first, the producer will block indefinitely. This method will block until all tasks * have completed or the specified waiting time elapses.. If all tasks have already completed, this method returns immediately. * * @param timeout the maximum time to wait * @param unit the time unit of the {@code timeout} argument * @throws InterruptedException If the producer thread is interrupted while waiting * @throws AbortedException If task processing has been abort which is accomplished by calling * {@link #abort(String)} */ public void waitForTasksToFinish(long timeout, TimeUnit unit) throws InterruptedException, AbortedException { try { lock.readLock().lock(); if (aborted) { throw new AbortedException(errorMessage); } if (remainingTasks == 0) { return; } } finally { lock.readLock().unlock(); } allTasksFinished.await(timeout, unit); try { lock.readLock().lock(); if (aborted) { throw new AbortedException(errorMessage); } } finally { lock.readLock().unlock(); } } /** * Should be invoked by a consumer to abort processing of any future tasks. * * @param msg An error message that will be included in the {@link AbortedException} thrown by * {@link #waitForTasksToFinish()} */ public void abort(String msg) { try { lock.writeLock().lock(); errorMessage = msg; aborted = true; allTasksFinished.countDown(); } finally { lock.writeLock().unlock(); } } }