package com.level3.meanwhile;
import com.level3.meanwhile.state.ClaimCheck;
import com.level3.meanwhile.concurrent.MeanwhileFuture;
import com.level3.meanwhile.concurrent.MeanwhileRunner;
import com.level3.meanwhile.concurrent.MeanwhileThreadPoolExecutor;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* Class that creates and manages a java thread pool for use in executing generic asynchronous tasks.
* There may be many of these pools available per JVM.
*
* <p>Currently backed by a boundless LinkedBlockingQueue. This means that the queue will continue to
* grow infinitely. May want to consider bounding this in the future and throwing exceptions
* if queue length exceeded.
*
* @author Jonathan Griggs <Jonathan.Griggs@Level3.com>
* @since 0.1
*/
public final class TaskQueue {
private int defaultNumThreads;
private int defaultStageThreads;
private LinkedBlockingQueue<Runnable> queue;
private MeanwhileThreadPoolExecutor pool;
private LinkedBlockingQueue<Runnable> stageQueue;
private MeanwhileThreadPoolExecutor stagePool;
//Static class, prevent instantiation
public TaskQueue(final int numThreads, final int numStageThreads){
this.defaultNumThreads = numThreads;
this.defaultStageThreads = numStageThreads;
};
/**
* Starts up the ThreadPool with the default number of threads - specified in properties
* @since 0.1
*/
private void startUp() {
startUp(defaultNumThreads,defaultStageThreads);
}
/**
* Starts up the ThreadPool with the specified number of threads
* @since 0.1
*/
private void startUp(final int maxNumThreads, final int maxNumStageThreads) {
if(!isPoolStarted()) {
queue = new LinkedBlockingQueue<Runnable>();
pool = new MeanwhileThreadPoolExecutor(maxNumThreads, maxNumThreads, 0, TimeUnit.SECONDS, queue);
}
if(!isStagePoolStarted()) {
stageQueue = new LinkedBlockingQueue<Runnable>();
stagePool = new MeanwhileThreadPoolExecutor(maxNumStageThreads, maxNumStageThreads, 0, TimeUnit.SECONDS, stageQueue);
}
}
/**
* Sets the maximum number of threads available for concurrent Task execution.
* If the number is lower than the current number of busy threads, the pool will wait until those threads finish
* and scale back the number of threads as they are returned to the pool
* @param max The maximum number of Tasks that should be executed concurrently. Must be > 0.
* @since 0.1
*/
public void setMaximumThreadCount(final int max) {
defaultNumThreads = max;
startUp();
pool.setCorePoolSize(max);
pool.setMaximumPoolSize(max);
}
/**
* Sets the maximum number of threads available for concurrent Task execution.
* If the number is lower than the current number of busy threads, the pool will wait until those threads finish
* and scale back the number of threads as they are returned to the pool
* @param max The maximum number of Tasks that should be executed concurrently. Must be > 0.
* @since 0.1
*/
public void setMaximumStagePoolThreadCount(final int max) {
defaultStageThreads = max;
startUp();
stagePool.setCorePoolSize(max);
stagePool.setMaximumPoolSize(max);
}
/**
* Adds a task into the executor thread pool, and returns a Claim Check
* @param task The Task to be executed
* @return a ClaimCheck that may be redeemed to cancel this task
* @since 0.1
* @see Task
* @see ClaimCheck
* @see Future
*/
public <T extends Task> ClaimCheck execute(final T task) {
startUp();
MeanwhileFuture<T> future = new MeanwhileFuture<T>(new MeanwhileRunner(task,this));
if(task instanceof Stage) {
stagePool.execute(future);
} else {
pool.execute(future);
}
return future.getClaimCheck();
}
/**
* Adds multiple tasks into the executor thread pool, and returns a list of Claim Checks
*
* @param tasks A list of Tasks to be executed
* @return a List of Futures that wrap the Tasks submitted to the work queue
* @since 0.1
* @see Task
* @see Future
*/
public <T extends Task> List<ClaimCheck> execute(final List<T> tasks) {
startUp();
List<ClaimCheck> claimChecks = new ArrayList<ClaimCheck>();
for(T task : tasks) {
claimChecks.add(this.execute(task));
}
return claimChecks;
}
/**
* Adds a task into the executor thread pool, and returns a Future
* @param task The Task to be executed
* @return the completed Task that was executed
* @since 0.1
* @see Task
* @see Future
*/
public <T extends Task> MeanwhileFuture<T> submit(final T task) {
startUp();
MeanwhileFuture<T> future = new MeanwhileFuture<T>(new MeanwhileRunner(task,this));
if(task instanceof Stage) {
stagePool.execute(future);
} else {
pool.execute(future);
}
return future;
}
/**
* Adds a task into the executor thread pool, and returns a Future
* @param mr An instance of MeanwhileRunner (which wraps a Task) to be executed
* @return the completed Task that was executed
* @since 0.1
* @see Task
* @see Future
*/
public <T extends Task> MeanwhileFuture<T> submit(final MeanwhileRunner<T> mr) {
startUp();
MeanwhileFuture<T> future = new MeanwhileFuture<T>(mr);
if(mr.getTask() instanceof Stage) {
stagePool.execute(future);
} else {
pool.execute(future);
}
return future;
}
/**
* Adds multiple tasks into the executor thread pool, and returns a list of Futures
*
* @param tasks A list of Tasks to be executed
* @return a List of Futures that wrap the Tasks submitted to the work queue
* @since 0.1
* @see Task
* @see Future
*/
public <T extends Task> List<MeanwhileFuture<T>> submit(final List<T> tasks) {
startUp();
List<MeanwhileFuture<T>> futures = new ArrayList<MeanwhileFuture<T>>();
for(T task : tasks) {
futures.add(this.submit(task));
}
return futures;
}
/**
* Takes a list of Tasks, submits each one to the Executor pool, and effectively WAITS until
* all those tasks are completed and then returns a list of the completed tasks in the order they were submitted (i.e., not in the order
* they finished)
*
* <p>This allows the client to parallelize work in the context of a single synchronous
* call.
* @param tasks A list of Tasks to be executed
* @return a List of completed Tasks that were executed
* @since 0.1
* @see Task
*/
public <T extends Task> List<T> blockingSubmit(final List<T> tasks) throws Exception {
startUp();
List<T> list = new ArrayList<T>();
for(Future<T> future : this.submit(tasks)) {
list.add(future.get());
}
return list;
}
/**
* Takes a single Task, submits it to the Executor pool, and effectively WAITS until
* the task is completed and then returns the Task
*
* <p>This allows the client to parallelize work in the context of a single synchronous
* call.
* @param task The Task to be executed
* @return the completed Task that was executed
* @since 0.1
* @see Task
*/
public <T extends Task> T blockingSubmit(final T task) throws Exception {
startUp();
return this.blockingSubmit(Arrays.asList(task)).get(0);
}
/**
*
* private helper method for blocking on ClaimCheck/UUID/UUID String
*
* @return <b>{@code true}</b> if the TaskManager has found the Task represented by the object and successfully waited for
* execution. A <b>{@code false}</b> is returned if the TaskManager did not find a Task represented by the object and therefore
* did not actually block (i.e., it returned immediately)
* @param obj an object representing ClaimCheck / UUID / UUID String of the Task we wish to block on
* @since 0.2.1
* @see ClaimCheck
*/
private boolean blockHelper(final Object obj) throws Exception {
startUp();
boolean blocked = false;
MeanwhileFuture future = stagePool.getFuture(obj);
if(future!=null) {
blocked = true;
future.get();
} else {
future = pool.getFuture(obj);
if(future!=null) {
blocked = true;
future.get();
}
}
return blocked;
}
/**
*
* Blocks until the task(s) represented by the ClaimCheck have finished processing.
*
* <p>This method is very useful for use cases where the client has a ClaimCheck and needs to halt further processing, but
* is for some reason unable to make an initial blockingSubmit() call. Unit Tests are a good example of this use case.
*
* @return <b>{@code true}</b> if the TaskManager has found the Task represented by the ClaimCheck and successfully waited for
* execution. A <b>{@code false}</b> is returned if the TaskManager did not find a Task represented by the ClaimCheck and therefore
* did not actually block (i.e., it returned immediately)
* @param claim the ClaimCheck of the Task we wish to block on
* @since 0.2.1
* @see ClaimCheck
*/
public boolean block(final ClaimCheck claim) throws Exception {
return blockHelper(claim);
}
/**
*
* Blocks until the task(s) represented by the UUID have finished processing.
*
* <p>This method is very useful for use cases where the client has a UUID and needs to halt further processing, but
* is for some reason unable to make an initial blockingSubmit() call. Unit Tests are a good example of this use case.
*
* @return <b>{@code true}</b> if the TaskManager has found the Task represented by the UUID and successfully waited for
* execution. A <b>{@code false}</b> is returned if the TaskManager did not find a Task represented by the UUID and therefore
* did not actually block (i.e., it returned immediately)
* @param uuid the UUID of the Task we wish to block on
* @since 0.2.1
* @see ClaimCheck
* @see UUID
*/
public boolean block(final UUID uuid) throws Exception {
return blockHelper(uuid);
}
/**
*
* Blocks until the task(s) represented by the UUID String have finished processing.
*
* <p>This method is very useful for use cases where the client has a UUID String and needs to halt further processing, but
* is for some reason unable to make an initial blockingSubmit() call. Unit Tests are a good example of this use case.
*
* @return <b>{@code true}</b> if the TaskManager has found the Task represented by the UUID String and successfully waited for
* execution. A <b>{@code false}</b> is returned if the TaskManager did not find a Task represented by the UUID String and therefore
* did not actually block (i.e., it returned immediately)
* @param sUuid the UUID String of the Task we wish to block on
* @since 0.2.1
* @see ClaimCheck
* @see UUID
*/
public boolean block(final String sUuid) throws Exception {
return blockHelper(sUuid);
}
/**
* private helper method
*
* <p>Cancels a task that has been scheduled for execution. If task is already in process,
* this will do nothing.
* @return <b>{@code true}</b> if the Task has been canceled, <b>{@code false}</b> if the Task could not be canceled,
* because it is already in TaskStatus.WORKING, or because it simply does not exist in the queue.
* @since 0.1
*/
private boolean cancelHelper(final Object task) {
startUp();
boolean canceled = false;
if(pool.cancel(task)) {
canceled = true;
}
if(stagePool.cancel(task)) {
canceled = true;
}
return canceled;
}
/**
*
* Cancels a task that has been scheduled for execution. If task is already in process,
* this will do nothing.
*
* <p>Cancel will prevent the execution of all incomplete, not-in-flight Tasks that match the Claim Check,
* as well as all subtasks that have yet to complete or begin processing.
*
* <p>For instance, consider a scenario with a Chain of Tasks:
*
* <p> Task 1 (Complete) -> Task 2 (Working) -> Task 3 (Queued) -> Task 4 (Queued)
*
* <p>Passing in the ClaimCheck for Task 1 (returned from TaskManager.execute()) will result in the cancellation of Task 3 and Task 4.
*
* @return <b>{@code true}</b> if the Task has been canceled, <b>{@code false}</b> if the Task could not be canceled,
* because it is already in TaskStatus.WORKING, or because it simply does not exist in the queue.
* @param claim the ClaimCheck of the task to be canceled
* @since 0.1
* @see Task
*/
public boolean cancel(final ClaimCheck claim) {
return cancelHelper(claim);
}
/**
*
* Cancels a queued task that matches the provided UUID. If task is already in process, this will do nothing.
*
* <p>Cancel will prevent the execution of all incomplete, not-in-flight Tasks that match the UUID,
* as well as all subtasks that have yet to complete or begin processing.
*
* @return <b>{@code true}</b> if the Task has been canceled, <b>{@code false}</b> if the Task could not be canceled,
* because it is already in TaskStatus.WORKING, or because it simply does not exist in the queue.
* @param uuid the UUID of the Task to be canceled
* @since 0.1
* @see UUID
*/
public boolean cancel(final UUID uuid) {
return cancelHelper(uuid);
}
/**
* Cancels a queued task that matches the provided UUID string. If task is already in process,
* this will do nothing.
*
* <p>Cancel will prevent the execution of all incomplete, not-in-flight Tasks that match the UUID string,
* as well as all subtasks that have yet to complete or begin processing.
*
* @return <b>{@code true}</b> if the Task has been canceled, <b>{@code false}</b> if the Task could not be canceled,
* because it is already in TaskStatus.WORKING, or because it simply does not exist in the queue.
* @param uuid a String representing the UUID of the Task
* @since 0.1
* @see UUID#toString
*/
public boolean cancel(final String uuid) {
return cancelHelper(uuid);
}
/**
* Returns the current queue size. This number represents the number of Tasks currently <i>waiting</i> to be
* executed, and not the number of Tasks currently being executed.
* @return the current size of the Task work queue
* @since 0.1
*/
public int getQueueSize() {
startUp();
return pool.getQueue().size();
}
/**
* Returns the current queue size. This number represents the number of Tasks currently <i>waiting</i> to be
* executed, and not the number of Tasks currently being executed.
* @return the current size of the Task work queue
* @since 0.1
*/
public int getStageQueueSize() {
startUp();
return stagePool.getQueue().size();
}
/**
* Returns the maximum number of threads that are available for concurrent execution of Tasks. This is not reflective of the
* current number of Tasks currently in WORKING state.
* @return the maximum number of Tasks that may be executed concurrently
* @since 0.1
*/
public int getMaximumPoolSize() {
startUp();
return pool.getMaximumPoolSize();
}
/**
* Returns the current number of threads that are busy executing Tasks.
* @return the number of threads that are currently busy executing tasks
* @since 0.1
*/
public int getActiveCount() {
startUp();
return pool.getActiveCount();
}
/**
* Gets a list of all of the queued Tasks
* @return a List of ClaimChecks for all of the queued Tasks
* @since 0.2
* @see ClaimCheck
*/
public List<ClaimCheck> getQueuedTasks() {
startUp();
List<ClaimCheck> list = new ArrayList<ClaimCheck>();
for(Runnable r : pool.getQueue()) {
if(r instanceof MeanwhileFuture) {
list.add(((MeanwhileFuture)r).getClaimCheck());
}
}
return list;
}
/**
* Gets a list of all of the Tasks currently in-flight
* @return a List of ClaimChecks for all of the active Tasks
* @since 0.2
* @see ClaimCheck
*/
public List<ClaimCheck> getActiveTasks() {
startUp();
List<ClaimCheck> list = new ArrayList<ClaimCheck>();
for(MeanwhileFuture future : pool.getActiveFutures()) {
list.add(future.getClaimCheck());
}
return list;
}
/**
* Reports whether the specified Task is queued for execution.
* @return <b>{@code true}</b> if the Task has been queued, <b>{@code false}</b> otherwise
* @since 0.1
* @see Task
*/
public boolean isQueued(final Object claimCheck) {
startUp();
for(Runnable r : pool.getQueue()) {
if(r.equals(claimCheck)) {
return true;
}
}
for(Runnable r : stagePool.getQueue()) {
if(r.equals(claimCheck)) {
return true;
}
}
return false;
}
/**
* Reports the current position of the specified task within the queue
* returns -1 if not in the queue
* @return the index of the Task within the work queue, -1 if not found
* @since 0.1
* @see Task
*/
public int getQueuePosition(final Object claimCheck) {
int i = 1;
for(Object r : pool.getQueue().toArray()) {
if(r.equals(claimCheck))
return i;
i++;
}
i = 1;
for(Object r : stagePool.getQueue().toArray()) {
if(r.equals(claimCheck))
return i;
i++;
}
return -1;
}
/**
* Shuts down the Executor pool immediately
* @since 0.1
*/
public void shutdown() {
if(pool!=null)
pool.shutdownNow();
if(stagePool!=null)
stagePool.shutdownNow();
}
/**
* Shuts down the Executor pool, specifying the amount of time to wait for tasks to finish
* @since 0.1
*/
public void shutdown(final long millisToWait) {
if(pool!=null) {
pool.shutdown();
try {
pool.awaitTermination(millisToWait, TimeUnit.MILLISECONDS);
} catch(Exception e) {
throw new RuntimeException("Error shutting down TaskManager " + e.toString());
}
pool.shutdownNow();
}
if(stagePool!=null) {
stagePool.shutdown();
try {
stagePool.awaitTermination(millisToWait, TimeUnit.MILLISECONDS);
} catch(Exception e) {
throw new RuntimeException("Error shutting down TaskManager " + e.toString());
}
stagePool.shutdownNow();
}
}
/**
* Returns the status of the pool.
* @return true if the pool has started, false if it is shutdown
* @since 0.1
*/
public boolean isPoolStarted() {
if(pool==null||pool.isShutdown()) {
return false;
}
return true;
}
/**
* Returns the status of the stage pool.
* @return true if the stage pool has started, false if it is shutdown
* @since 0.1
*/
public boolean isStagePoolStarted() {
if(stagePool==null||stagePool.isShutdown()) {
return false;
}
return true;
}
}