package org.threadly.concurrent;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ThreadFactory;
import org.threadly.concurrent.collections.ConcurrentArrayList;
/**
* In order to avoid a performance hit by verifying state which would indicate a programmer
* error at runtime. This class functions to verify those little things during unit tests.
* For that reason this class extends {@link PriorityScheduler} to do additional
* functions, but calls into the super functions to verify the actual behavior.
*
* @author jent - Mike Jensen
*/
public class StrictPriorityScheduler extends PriorityScheduler {
/**
* Constructs a new thread pool, though no threads will be started till it accepts it's first
* request. This constructs a default priority of high (which makes sense for most use cases).
* It also defaults low priority worker wait as 500ms. It also defaults to all newly created
* threads being daemon threads.
*
* @param poolSize Thread pool size that should be maintained
*/
public StrictPriorityScheduler(int poolSize) {
this(poolSize, null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS, DEFAULT_NEW_THREADS_DAEMON);
}
/**
* Constructs a new thread pool, though no threads will be started till it accepts it's first
* request. This constructs a default priority of high (which makes sense for most use cases).
* It also defaults low priority worker wait as 500ms.
*
* @param poolSize Thread pool size that should be maintained
* @param useDaemonThreads {@code true} if newly created threads should be daemon
*/
public StrictPriorityScheduler(int poolSize, boolean useDaemonThreads) {
this(poolSize, null, DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS, useDaemonThreads);
}
/**
* Constructs a new thread pool, though no threads will be started till it accepts it's first
* request. This provides the extra parameters to tune what tasks submitted without a priority
* will be scheduled as. As well as the maximum wait for low priority tasks. The longer low
* priority tasks wait for a worker, the less chance they will have to create a thread. But it
* also makes low priority tasks execution time less predictable.
*
* @param poolSize Thread pool size that should be maintained
* @param defaultPriority priority to give tasks which do not specify it
* @param maxWaitForLowPriorityInMs time low priority tasks wait for a worker
*/
public StrictPriorityScheduler(int poolSize, TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs) {
this(poolSize, defaultPriority, maxWaitForLowPriorityInMs, DEFAULT_NEW_THREADS_DAEMON);
}
/**
* Constructs a new thread pool, though no threads will be started till it accepts it's first
* request. This provides the extra parameters to tune what tasks submitted without a priority
* will be scheduled as. As well as the maximum wait for low priority tasks. The longer low
* priority tasks wait for a worker, the less chance they will have to create a thread. But it
* also makes low priority tasks execution time less predictable.
*
* @param poolSize Thread pool size that should be maintained
* @param defaultPriority priority to give tasks which do not specify it
* @param maxWaitForLowPriorityInMs time low priority tasks wait for a worker
* @param useDaemonThreads {@code true} if newly created threads should be daemon
*/
public StrictPriorityScheduler(int poolSize, TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs,
boolean useDaemonThreads) {
this(poolSize, defaultPriority, maxWaitForLowPriorityInMs,
new ConfigurableThreadFactory(PriorityScheduler.class.getSimpleName() + "-",
true, useDaemonThreads, Thread.NORM_PRIORITY, null, null));
}
/**
* Constructs a new thread pool, though no threads will be started till it accepts it's first
* request. This provides the extra parameters to tune what tasks submitted without a priority
* will be scheduled as. As well as the maximum wait for low priority tasks. The longer low
* priority tasks wait for a worker, the less chance they will have to create a thread. But it
* also makes low priority tasks execution time less predictable.
*
* @param poolSize Thread pool size that should be maintained
* @param defaultPriority priority to give tasks which do not specify it
* @param maxWaitForLowPriorityInMs time low priority tasks wait for a worker
* @param threadFactory thread factory for producing new threads within executor
*/
public StrictPriorityScheduler(int poolSize, TaskPriority defaultPriority,
long maxWaitForLowPriorityInMs, ThreadFactory threadFactory) {
super(new WorkerPool(threadFactory, poolSize), defaultPriority, maxWaitForLowPriorityInMs);
}
private static void verifyOneTimeTaskQueueSet(QueueSet queueSet, OneTimeTaskWrapper task) {
if (task.taskQueue instanceof ConcurrentLinkedQueue) {
if (queueSet.executeQueue != task.taskQueue) {
throw new IllegalStateException("Queue missmatch");
}
} else if (task.taskQueue instanceof ConcurrentArrayList) {
if (queueSet.scheduleQueue != task.taskQueue) {
throw new IllegalStateException("Queue missmatch");
}
} else if (task.taskQueue != null) {
throw new UnsupportedOperationException("Unhandled queue type");
}
}
@Override
protected void addToExecuteQueue(QueueSet queueSet, OneTimeTaskWrapper task) {
verifyOneTimeTaskQueueSet(queueSet, task);
super.addToExecuteQueue(queueSet, task);
}
@Override
protected void addToScheduleQueue(QueueSet queueSet, TaskWrapper task) {
if (task instanceof OneTimeTaskWrapper) {
verifyOneTimeTaskQueueSet(queueSet, (OneTimeTaskWrapper)task);
} else if (task instanceof RecurringTaskWrapper) {
RecurringTaskWrapper recurringTask = (RecurringTaskWrapper)task;
if (queueSet != recurringTask.queueSet) {
throw new IllegalStateException("QueueSet mismatch");
}
if (task instanceof RecurringDelayTaskWrapper) {
task = new StrictRecurringDelayTaskWrapper(task.task, recurringTask.queueSet,
recurringTask.nextRunTime,
((RecurringDelayTaskWrapper)recurringTask).recurringDelay);
} else {
task = new StrictRecurringRateTaskWrapper(task.task, recurringTask.queueSet,
recurringTask.nextRunTime,
((RecurringRateTaskWrapper)recurringTask).period);
}
} else {
throw new UnsupportedOperationException("Unhandled task type");
}
super.addToScheduleQueue(queueSet, task);
}
protected static class StrictRecurringDelayTaskWrapper extends RecurringDelayTaskWrapper {
protected StrictRecurringDelayTaskWrapper(Runnable task, QueueSet queueSet, long firstRunTime,
long recurringDelay) {
super(task, queueSet, firstRunTime, recurringDelay);
}
@Override
public boolean canExecute(short executeReference) {
synchronized (queueSet.scheduleQueue.getModificationLock()) {
if (super.canExecute(executeReference)) {
int index = queueSet.scheduleQueue.lastIndexOf(this);
if (index != queueSet.scheduleQueue.size() - 1) {
for (int i = index + 1; i < queueSet.scheduleQueue.size(); i++) {
if (queueSet.scheduleQueue.get(i).getRunTime() != Long.MAX_VALUE) {
IllegalStateException e =
new IllegalStateException("Invalid queue state: " + queueSet.scheduleQueue);
e.printStackTrace();
throw e;
}
}
}
return true;
} else {
return false;
}
}
}
}
protected static class StrictRecurringRateTaskWrapper extends RecurringRateTaskWrapper {
protected StrictRecurringRateTaskWrapper(Runnable task, QueueSet queueSet, long firstRunTime,
long period) {
super(task, queueSet, firstRunTime, period);
}
@Override
public boolean canExecute(short executeReference) {
synchronized (queueSet.scheduleQueue.getModificationLock()) {
if (super.canExecute(executeReference)) {
int index = queueSet.scheduleQueue.lastIndexOf(this);
if (index != queueSet.scheduleQueue.size() - 1) {
for (int i = index + 1; i < queueSet.scheduleQueue.size(); i++) {
if (queueSet.scheduleQueue.get(i).getRunTime() != Long.MAX_VALUE) {
IllegalStateException e =
new IllegalStateException("Invalid queue state: " + queueSet.scheduleQueue);
e.printStackTrace();
throw e;
}
}
}
return true;
} else {
return false;
}
}
}
}
}