org.pepsoft.util.ParallelProgressManager Maven / Gradle / Ivy
/*
* To change this template, choose Tools | Templates
* and open the template in the editor.
*/
package org.pepsoft.util;
import org.pepsoft.util.ProgressReceiver.OperationCancelled;
import org.pepsoft.util.ProgressReceiver.OperationCancelledByUser;
import java.util.BitSet;
import static org.pepsoft.util.ExceptionUtils.chainContains;
/**
* A manager of parallel progress receivers, which reports to one parent
* progress receiver, combining the progress values and managing the reporting
* of exceptions or task completion.
*
* - Instantiate it with a parent progress receiver, and a task count (if
* known). Note: the parent progress receiver should be thread
* safe and not make assumptions about which threads its methods will be invoked
* from!
*
*
- Invoke createProgressReceiver() as many times as needed.
* Note: if the manager has not been created with a task count
* you cannot invoke this method any more after the first task has started!
*
*
- Start the tasks in background threads and invoke {@link #join()} on the
* manager to wait for all tasks to complete (defined as either invoking
* {@link ProgressReceiver#done()} or {@link ProgressReceiver#exceptionThrown(Throwable)}
* on their progress receivers).
*
* If a task invokes {@link ProgressReceiver#exceptionThrown(Throwable)} it will
* be reported to the parent progress receiver, and all subsequent invocations
* on their progress receivers by any other tasks will result in an
* {@link OperationCancelled} exception being thrown. If any
* more exceptions are reported these are not reported to the parent
* progress receiver (instead they are logged using the java.util logging
* framework). Also, if an exception has been reported,
* {@link ProgressReceiver#done()} will not subsequently be invoked on the
* parent progress receiver.
*
*
If no exceptions are reported, {@link ProgressReceiver#done()} will be
* invoked on the parent progress receiver after the last task has invoked it on
* its sub progress receiver.
*
*
All invocations on {@link ProgressReceiver#setMessage(String)} are passed
* through unaltered to the parent progress receiver.
*
*
If the parent progress receiver throws an {@code OperationCancelled}
* exception at any time, it is stored and rethrown to every task whenever they
* next invoke a method (that declares it) on their sub progress receivers. It
* is immediately rethrown to the calling task.
*
* @author pepijn
*/
public class ParallelProgressManager {
public ParallelProgressManager(ProgressReceiver progressReceiver) {
this.progressReceiver = progressReceiver;
taskCountKnown = false;
}
public ParallelProgressManager(ProgressReceiver progressReceiver, int taskCount) {
this.progressReceiver = progressReceiver;
this.taskCount = taskCount;
taskCountKnown = true;
taskProgress = new float[taskCount];
running.set(0, taskCount);
started = true;
}
public synchronized ProgressReceiver createProgressReceiver() {
if ((! taskCountKnown) && started) {
throw new IllegalStateException("Cannot create new progress receivers after tasks have started");
}
if (taskCountKnown && (tasksCreated == taskCount)) {
throw new IllegalStateException("Attempt to create more sub progress receivers than indicated task count (" + taskCount + ")");
}
return new SubProgressReceiver(tasksCreated++);
}
public synchronized void join() throws InterruptedException {
while (true) {
if (! started) {
wait();
} else {
if (running.isEmpty()) {
return;
} else {
wait();
}
}
}
}
public synchronized boolean isExceptionThrown() {
return exceptionThrown;
}
private synchronized void setProgress(int index, float subProgress) throws OperationCancelled {
if (! started) {
start();
}
cancelIfPreviousException();
taskProgress[index] = subProgress;
float totalProgress = 0.0f;
for (float progress: taskProgress) {
totalProgress += progress;
}
try {
progressReceiver.setProgress(totalProgress / taskCount);
} catch (OperationCancelled e) {
previousException = e;
throw e;
}
}
private synchronized void exceptionThrown(int index, Throwable exception) {
if (! started) {
start();
}
exceptionThrown = true;
if (previousException == null) {
previousException = exception;
}
running.clear(index);
notifyAll();
if (! exceptionReported) {
exceptionReported = true;
progressReceiver.exceptionThrown(exception);
} else if (chainContains(exception, OperationCancelledByUser.class)) {
if (logger.isDebugEnabled()) {
logger.debug("Operation cancelled by user; not reporting to progress receiver");
}
} else if (chainContains(exception, OperationCancelled.class)) {
logger.debug("Operation cancelled on thread {} (message: \"{}\")", Thread.currentThread().getName(), exception.getMessage());
} else {
logger.error("Secondary exception from parallel task; not reporting to progress receiver", exception);
}
}
private synchronized void done(int index) {
if (! started) {
start();
}
running.clear(index);
notifyAll();
if (! exceptionReported) {
if (running.isEmpty()) {
progressReceiver.done();
}
}
}
private synchronized void setMessage(int index, String message) throws OperationCancelled {
if (! started) {
start();
}
cancelIfPreviousException();
progressReceiver.setMessage(message);
}
private synchronized void checkForCancellation() throws OperationCancelled {
if (! started) {
start();
}
cancelIfPreviousException();
}
private synchronized void subProgressStarted(org.pepsoft.util.SubProgressReceiver subProgressReceiver) throws OperationCancelled {
if (! started) {
start();
}
cancelIfPreviousException();
progressReceiver.subProgressStarted(subProgressReceiver);
}
private synchronized void start() {
taskCount = tasksCreated;
taskProgress = new float[taskCount];
running.set(0, taskCount);
started = true;
notifyAll();
}
private void cancelIfPreviousException() throws OperationCancelled {
if (previousException != null) {
throw new OperationCancelled("Operation cancelled due to exception on other thread (type: " + previousException.getClass().getSimpleName() + ", message: " + previousException.getMessage() + ")", previousException);
}
}
private final ProgressReceiver progressReceiver;
private final boolean taskCountKnown;
private final BitSet running = new BitSet();
private int taskCount, tasksCreated;
private float[] taskProgress;
private Throwable previousException;
private boolean started, exceptionThrown, exceptionReported;
private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(ParallelProgressManager.class);
private class SubProgressReceiver implements ProgressReceiver {
private SubProgressReceiver(int index) {
this.index = index;
}
@Override
public void setProgress(float progress) throws OperationCancelled {
ParallelProgressManager.this.setProgress(index, progress);
}
@Override
public void exceptionThrown(Throwable exception) {
ParallelProgressManager.this.exceptionThrown(index, exception);
}
@Override
public void done() {
ParallelProgressManager.this.done(index);
}
@Override
public void setMessage(String message) throws OperationCancelled {
ParallelProgressManager.this.setMessage(index, message);
}
@Override
public void checkForCancellation() throws OperationCancelled {
ParallelProgressManager.this.checkForCancellation();
}
@Override
public void reset() {
throw new UnsupportedOperationException("Not supported");
}
@Override
public void subProgressStarted(org.pepsoft.util.SubProgressReceiver subProgressReceiver) throws OperationCancelled {
ParallelProgressManager.this.subProgressStarted(subProgressReceiver);
}
private final int index;
}
}