All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.threadly.concurrent.AbstractPriorityScheduler Maven / Gradle / Ivy

package org.threadly.concurrent;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Future;
import java.util.concurrent.locks.LockSupport;
import java.util.function.Function;

import org.threadly.concurrent.collections.ConcurrentArrayList;
import org.threadly.concurrent.future.ListenableFuture;
import org.threadly.concurrent.future.ListenableFutureTask;
import org.threadly.concurrent.future.ListenableRunnableFuture;
import org.threadly.util.ArgumentVerifier;
import org.threadly.util.Clock;
import org.threadly.util.ExceptionUtils;
import org.threadly.util.SortUtils;

/**
 * Abstract implementation for implementations of {@link PrioritySchedulerService}.  In general 
 * this wont be useful outside of Threadly developers, but must be a public interface since it is 
 * used in sub-packages.
 * 

* If you do find yourself using this class, please post an issue on github to tell us why. If * there is something you want our schedulers to provide, we are happy to hear about it. * * @since 4.3.0 */ public abstract class AbstractPriorityScheduler extends AbstractSubmitterScheduler implements PrioritySchedulerService { protected static final TaskPriority DEFAULT_PRIORITY = TaskPriority.High; protected static final int DEFAULT_LOW_PRIORITY_MAX_WAIT_IN_MS = 500; // tuned for performance of scheduled tasks protected static final int QUEUE_FRONT_PADDING = 0; protected static final int QUEUE_REAR_PADDING = 2; static { @SuppressWarnings("unused") // https://bugs.openjdk.java.net/browse/JDK-8074773 Class ensureLoaded = LockSupport.class; } protected final TaskPriority defaultPriority; protected AbstractPriorityScheduler(TaskPriority defaultPriority) { if (defaultPriority == null) { this.defaultPriority = DEFAULT_PRIORITY; } else { this.defaultPriority = defaultPriority; } } /** * Changes the max wait time for low priority tasks. This is the amount of time that a low * priority task will wait if there are ready to execute high priority tasks. After a low * priority task has waited this amount of time, it will be executed fairly with high priority * tasks (meaning it will only execute the high priority task if it has been waiting longer than * the low priority task). * * @param maxWaitForLowPriorityInMs new wait time in milliseconds for low priority tasks during thread contention */ public void setMaxWaitForLowPriority(long maxWaitForLowPriorityInMs) { getQueueManager().setMaxWaitForLowPriority(maxWaitForLowPriorityInMs); } @Override public long getMaxWaitForLowPriority() { return getQueueManager().getMaxWaitForLowPriority(); } @Override public TaskPriority getDefaultPriority() { return defaultPriority; } @Override protected final void doSchedule(Runnable task, long delayInMillis) { doSchedule(task, delayInMillis, defaultPriority); } /** * Constructs a {@link OneTimeTaskWrapper} and adds it to the most efficient queue. If there is * no delay it will use {@link #addToExecuteQueue(OneTimeTaskWrapper)}, if there is a delay it * will be added to {@link #addToScheduleQueue(TaskWrapper)}. * * @param task Runnable to be executed * @param delayInMillis delay to wait before task is run * @param priority Priority for task execution * @return Wrapper that was scheduled */ protected abstract OneTimeTaskWrapper doSchedule(Runnable task, long delayInMillis, TaskPriority priority); @Override public void execute(Runnable task, TaskPriority priority) { schedule(task, 0, priority); } @Override public ListenableFuture submit(Runnable task, T result, TaskPriority priority) { return submitScheduled(task, result, 0, priority); } @Override public ListenableFuture submit(Callable task, TaskPriority priority) { return submitScheduled(task, 0, priority); } @Override public void schedule(Runnable task, long delayInMs, TaskPriority priority) { ArgumentVerifier.assertNotNull(task, "task"); ArgumentVerifier.assertNotNegative(delayInMs, "delayInMs"); if (priority == null) { priority = defaultPriority; } doSchedule(task, delayInMs, priority); } @Override public ListenableFuture submitScheduled(Runnable task, T result, long delayInMs, TaskPriority priority) { return submitScheduled(RunnableCallableAdapter.adapt(task, result), delayInMs, priority); } @Override public ListenableFuture submitScheduled(Callable task, long delayInMs, TaskPriority priority) { ArgumentVerifier.assertNotNull(task, "task"); ArgumentVerifier.assertNotNegative(delayInMs, "delayInMs"); if (priority == null) { priority = defaultPriority; } ListenableRunnableFuture rf = new ListenableFutureTask<>(task, this); doSchedule(rf, delayInMs, priority); return rf; } @Override public void scheduleWithFixedDelay(Runnable task, long initialDelay, long recurringDelay) { scheduleWithFixedDelay(task, initialDelay, recurringDelay, null); } @Override public void scheduleAtFixedRate(Runnable task, long initialDelay, long period) { scheduleAtFixedRate(task, initialDelay, period, null); } @Override public boolean remove(Runnable task) { return getQueueManager().remove(task); } @Override public boolean remove(Callable task) { return getQueueManager().remove(task); } /** * Call to get reference to {@link QueueManager}. This reference can be used to get access to * queues directly, or perform operations which are distributed to multiple queues. This * reference can not be maintained in this abstract class to allow the potential for * {@link QueueSetListener}'s which want to reference things in {@code this}. * * @return Manager for queue sets */ protected abstract QueueManager getQueueManager(); @Override public int getQueuedTaskCount() { int result = 0; for (TaskPriority p : TaskPriority.values()) { result += getQueueManager().getQueueSet(p).queueSize(); } return result; } @Override public int getQueuedTaskCount(TaskPriority priority) { if (priority == null) { return getQueuedTaskCount(); } return getQueueManager().getQueueSet(priority).queueSize(); } @Override public int getWaitingForExecutionTaskCount() { int result = 0; for (TaskPriority p : TaskPriority.values()) { result += getWaitingForExecutionTaskCount(p); } return result; } @Override public int getWaitingForExecutionTaskCount(TaskPriority priority) { if (priority == null) { return getWaitingForExecutionTaskCount(); } QueueSet qs = getQueueManager().getQueueSet(priority); int result = qs.executeQueue.size(); for (int i = 0; i < qs.scheduleQueue.size(); i++) { try { if (qs.scheduleQueue.get(i).getScheduleDelay() > 0) { break; } else { result++; } } catch (IndexOutOfBoundsException e) { break; } } return result; } /** * Interface to be notified when relevant changes happen to the queue set. * * @since 4.3.0 */ protected interface QueueSetListener { /** * Invoked when the head of the queue set has been updated. This can be used to wake up * blocking threads waiting for tasks to consume. */ public void handleQueueUpdate(); } /** * Class to contain structures for both execution and scheduling. It also contains logic for * how we get and add tasks to this queue. *

* This allows us to have one structure for each priority. Each structure determines what is * the next task for a given priority. * * @since 4.0.0 */ protected static class QueueSet { protected final QueueSetListener queueListener; protected final ConcurrentLinkedQueue executeQueue; protected final ConcurrentArrayList scheduleQueue; protected final Function scheduleQueueRunTimeByIndex; public QueueSet(QueueSetListener queueListener) { this.queueListener = queueListener; this.executeQueue = new ConcurrentLinkedQueue<>(); this.scheduleQueue = new ConcurrentArrayList<>(QUEUE_FRONT_PADDING, QUEUE_REAR_PADDING); scheduleQueueRunTimeByIndex = (index) -> scheduleQueue.get(index).getRunTime(); } /** * Adds a task for immediate execution. No safety checks are done at this point, the task * will be immediately added and available for consumption. * * @param task Task to add to end of execute queue */ public void addExecute(OneTimeTaskWrapper task) { executeQueue.add(task); queueListener.handleQueueUpdate(); } /** * Adds a task for delayed execution. No safety checks are done at this point. This call * will safely find the insertion point in the scheduled queue and insert it into that * queue. * * @param task Task to insert into the schedule queue */ public void addScheduled(TaskWrapper task) { int insertionIndex; synchronized (scheduleQueue.getModificationLock()) { insertionIndex = SortUtils.getInsertionEndIndex(scheduleQueueRunTimeByIndex, scheduleQueue.size() - 1, task.getRunTime(), true); scheduleQueue.add(insertionIndex, task); } if (insertionIndex == 0) { queueListener.handleQueueUpdate(); } } /** * Removes a given callable from the internal queues (if it exists). * * @param task Callable to search for and remove * @return {@code true} if the task was found and removed */ public boolean remove(Callable task) { { Iterator it = executeQueue.iterator(); while (it.hasNext()) { TaskWrapper tw = it.next(); if (ContainerHelper.isContained(tw.task, task) && executeQueue.remove(tw)) { tw.invalidate(); return true; } } } synchronized (scheduleQueue.getModificationLock()) { Iterator it = scheduleQueue.iterator(); while (it.hasNext()) { TaskWrapper tw = it.next(); if (ContainerHelper.isContained(tw.task, task)) { tw.invalidate(); it.remove(); return true; } } } return false; } /** * Removes a given Runnable from the internal queues (if it exists). * * @param task Runnable to search for and remove * @return {@code true} if the task was found and removed */ public boolean remove(Runnable task) { { Iterator it = executeQueue.iterator(); while (it.hasNext()) { TaskWrapper tw = it.next(); if (ContainerHelper.isContained(tw.task, task) && executeQueue.remove(tw)) { tw.invalidate(); return true; } } } synchronized (scheduleQueue.getModificationLock()) { Iterator it = scheduleQueue.iterator(); while (it.hasNext()) { TaskWrapper tw = it.next(); if (ContainerHelper.isContained(tw.task, task)) { tw.invalidate(); it.remove(); return true; } } } return false; } /** * Call to get the total quantity of tasks within both stored queues. This returns the total * quantity of items in both the execute and scheduled queue. If there are scheduled tasks * which are NOT ready to run, they will still be included in this total. * * @return Total quantity of tasks queued */ public int queueSize() { return executeQueue.size() + scheduleQueue.size(); } public void drainQueueInto(List removedTasks) { clearQueue(executeQueue, removedTasks); synchronized (scheduleQueue.getModificationLock()) { clearQueue(scheduleQueue, removedTasks); } } private static void clearQueue(Collection queue, List resultList) { boolean resultWasEmpty = resultList.isEmpty(); Iterator it = queue.iterator(); while (it.hasNext()) { TaskWrapper tw = it.next(); // no need to cancel and return tasks which are already canceled if (! (tw.task instanceof Future) || ! ((Future)tw.task).isCancelled()) { tw.invalidate(); // don't return tasks which were used only for internal behavior management if (! (tw.task instanceof InternalRunnable)) { if (resultWasEmpty) { resultList.add(tw); } else { resultList.add(SortUtils.getInsertionEndIndex((index) -> resultList.get(index).getRunTime(), resultList.size() - 1, tw.getRunTime(), true), tw); } } } } queue.clear(); } /** * Gets the next task from this {@link QueueSet}. This inspects both the execute queue and * against scheduled tasks to determine which task in this {@link QueueSet} should be executed * next. *

* The task returned from this may not be ready to executed, but at the time of calling it * will be the next one to execute. * * @return TaskWrapper which will be executed next, or {@code null} if there are no tasks */ public TaskWrapper getNextTask() { TaskWrapper scheduledTask = scheduleQueue.peekFirst(); TaskWrapper executeTask = executeQueue.peek(); if (executeTask != null) { if (scheduledTask != null && scheduledTask.getRunTime() < executeTask.getRunTime()) { return scheduledTask; } else { return executeTask; } } else { return scheduledTask; } } } /** * A service which manages the execute queues. It runs a task to consume from the queues and * execute those tasks as workers become available. It also manages the queues as tasks are * added, removed, or rescheduled. * * @since 3.4.0 */ protected static class QueueManager { protected final QueueSet highPriorityQueueSet; protected final QueueSet lowPriorityQueueSet; protected final QueueSet starvablePriorityQueueSet; private volatile long maxLowPriorityWaitMillis; public QueueManager(QueueSetListener queueSetListener, long maxWaitForLowPriorityInMs) { this.highPriorityQueueSet = new QueueSet(queueSetListener); this.lowPriorityQueueSet = new QueueSet(queueSetListener); this.starvablePriorityQueueSet = new QueueSet(queueSetListener); // call to verify and set values setMaxWaitForLowPriority(maxWaitForLowPriorityInMs); } /** * Returns the {@link QueueSet} for a specified priority. * * @param priority Priority that should match to the given {@link QueueSet} * @return {@link QueueSet} which matches to the priority */ public QueueSet getQueueSet(TaskPriority priority) { if (priority == TaskPriority.High) { return highPriorityQueueSet; } else if (priority == TaskPriority.Low) { return lowPriorityQueueSet; } else { return starvablePriorityQueueSet; } } /** * Removes any tasks waiting to be run. Will not interrupt any tasks currently running. But * will avoid additional tasks from being run (unless they are allowed to be added during or * after this call). *

* If tasks are added concurrently during this invocation they may or may not be removed. * * @return List of runnables which were waiting in the task queue to be executed (and were now removed) */ public List clearQueue() { List wrapperList = new ArrayList<>(highPriorityQueueSet.queueSize() + lowPriorityQueueSet.queueSize() + starvablePriorityQueueSet.queueSize()); highPriorityQueueSet.drainQueueInto(wrapperList); lowPriorityQueueSet.drainQueueInto(wrapperList); starvablePriorityQueueSet.drainQueueInto(wrapperList); return ContainerHelper.getContainedRunnables(wrapperList); } /** * Gets the next task currently queued for execution. This task may be ready to execute, or * just queued. If a queue update comes in, this must be re-invoked to see what task is now * next. If there are no tasks ready to be executed this will simply return {@code null}. * * @param allowStarvable {@code true} to return starvable tasks if no other task is available and ready * @return Task to be executed next, or {@code null} if no tasks at all are queued */ public TaskWrapper getNextTask(boolean allowStarvable) { // First compare between high and low priority task queues // then depending on that state, we may check starvable TaskWrapper nextTask; TaskWrapper nextHighTask = highPriorityQueueSet.getNextTask(); TaskWrapper nextLowTask = lowPriorityQueueSet.getNextTask(); if (nextLowTask == null) { nextTask = nextHighTask; } else if (nextHighTask == null) { nextTask = nextLowTask; } else if (nextHighTask.getRunTime() <= nextLowTask.getRunTime()) { nextTask = nextHighTask; } else if (nextLowTask.getPureRunTime() + maxLowPriorityWaitMillis < nextHighTask.getPureRunTime() || // before the above check we know the low priority has been waiting longer than the high // priority. If the low priority has been waiting longer than the timeout, it can now // be returned as the next ready task // // OR // // If the high priority task is not ready to execute anyways, then we will provide the // low. The low priority will either be ready to run, or will be ready to run sooner. nextHighTask.getScheduleDelay() > 0) { nextTask = nextLowTask; } else { // task is ready to run, low priority is also ready, but has not been waiting long enough nextTask = nextHighTask; } if (! allowStarvable) { return nextTask; } else if (nextTask == null) { return starvablePriorityQueueSet.getNextTask(); } else if (nextTask.getScheduleDelay() > 0) { TaskWrapper nextStarvableTask = starvablePriorityQueueSet.getNextTask(); if (nextStarvableTask != null && nextStarvableTask.getPureRunTime() < nextTask.getPureRunTime()) { return nextStarvableTask; } else { return nextTask; } } else { return nextTask; } } /** * Removes the runnable task from the execution queue. It is possible for the runnable to * still run until this call has returned. *

* Note that this call has high guarantees on the ability to remove the task (as in a complete * guarantee). But while this is being invoked, it will reduce the throughput of execution, * so should NOT be used extremely frequently. * * @param task The original runnable provided to the executor * @return {@code true} if the runnable was found and removed */ public boolean remove(Runnable task) { return highPriorityQueueSet.remove(task) || lowPriorityQueueSet.remove(task) || starvablePriorityQueueSet.remove(task); } /** * Removes the callable task from the execution queue. It is possible for the callable to * still run until this call has returned. *

* Note that this call has high guarantees on the ability to remove the task (as in a complete * guarantee). But while this is being invoked, it will reduce the throughput of execution, * so should NOT be used extremely frequently. * * @param task The original callable provided to the executor * @return {@code true} if the callable was found and removed */ public boolean remove(Callable task) { return highPriorityQueueSet.remove(task) || lowPriorityQueueSet.remove(task) || starvablePriorityQueueSet.remove(task); } /** * Changes the max wait time for low priority tasks. This is the amount of time that a low * priority task will wait if there are ready to execute high priority tasks. After a low * priority task has waited this amount of time, it will be executed fairly with high priority * tasks (meaning it will only execute the high priority task if it has been waiting longer than * the low priority task). * * @param maxWaitForLowPriorityInMs new wait time in milliseconds for low priority tasks during thread contention */ public void setMaxWaitForLowPriority(long maxWaitForLowPriorityInMs) { ArgumentVerifier.assertNotNegative(maxWaitForLowPriorityInMs, "maxWaitForLowPriorityInMs"); this.maxLowPriorityWaitMillis = maxWaitForLowPriorityInMs; } /** * Getter for the amount of time a low priority task will wait during thread contention before * it is eligible for execution. * * @return currently set max wait for low priority task */ public long getMaxWaitForLowPriority() { return maxLowPriorityWaitMillis; } } /** * Abstract implementation for all tasks handled by this pool. * * @since 1.0.0 */ protected abstract static class TaskWrapper implements RunnableContainer { protected final Runnable task; protected volatile boolean invalidated; public TaskWrapper(Runnable task) { this.task = task; invalidated = false; } /** * Similar to {@link Runnable#run()}, this is invoked to execute the contained task. One * critical difference is this implementation should never throw an exception (even * {@link RuntimeException}'s). Throwing such an exception would result in the worker thread * dying (and being leaked from the pool). */ public abstract void runTask(); /** * Attempts to invalidate the task from running (assuming it has not started yet). If the * task is recurring then future executions will also be avoided. */ public void invalidate() { invalidated = true; } /** * Get an execution reference so that we can ensure thread safe access into * {@link #canExecute(short)}. * * @return Short to identify execution state */ public abstract short getExecuteReference(); /** * Called as the task is being removed from the queue to prepare for execution. The reference * provided here should be captured from {@link #getExecuteReference()}. * * @param executeReference Reference checked to ensure thread safe task execution * @return true if the task should be executed */ public abstract boolean canExecute(short executeReference); /** * Get the absolute time when this should run, in comparison with the time returned from * {@link org.threadly.util.Clock#accurateForwardProgressingMillis()}. * * @return Absolute time in millis this task should run */ public abstract long getRunTime(); /** * Simple getter for the run time, this is expected to do NO operations for calculating the * run time. The main reason this is used over {@link #getRunTime()} is to allow the JVM to * jit the function better. Because of the nature of this, this can only be used at very * specific points in the tasks lifecycle, and can not be used for sorting operations. * * @return An un-molested representation of the stored absolute run time */ public abstract long getPureRunTime(); /** * Call to see how long the task should be delayed before execution. While this may return * either positive or negative numbers, only an accurate number is returned if the task must * be delayed for execution. If the task is ready to execute it may return zero even though * it is past due. For that reason you can NOT use this to compare two tasks for execution * order, instead you should use {@link #getRunTime()}. * * @return delay in milliseconds till task can be run */ public abstract long getScheduleDelay(); @Override public String toString() { return task.toString(); } @Override public Runnable getContainedRunnable() { return task; } } /** * Wrapper for tasks which only executes once. * * @since 1.0.0 */ protected abstract static class OneTimeTaskWrapper extends TaskWrapper { protected final Queue taskQueue; protected final long runTime; // optimization to avoid queue traversal on failure to remove, cheaper than AtomicBoolean protected volatile boolean executed; // default false public OneTimeTaskWrapper(Runnable task, Queue taskQueue, long runTime) { super(task); this.taskQueue = taskQueue; this.runTime = runTime; } @Override public long getPureRunTime() { return runTime; } @Override public long getRunTime() { return runTime; } @Override public void runTask() { if (! invalidated) { try { task.run(); } catch (Throwable t) { ExceptionUtils.handleException(t); } } } @Override public short getExecuteReference() { // we ignore the reference since one time tasks are deterministically removed from the queue return 0; } @Override public boolean canExecute(short ignoredExecuteReference) { if (! executed && (executed = true) & // set executed as soon as possible, before removal attempt taskQueue.remove(this)) { // every task is wrapped in a unique wrapper, so we can remove 'this' safely return true; } else { return false; } } } /** * Implementation of {@link OneTimeTaskWrapper} that provides an accurate schedule delay. * * @since 5.41 */ protected static class AccurateOneTimeTaskWrapper extends OneTimeTaskWrapper { public AccurateOneTimeTaskWrapper(Runnable task, Queue taskQueue, long runTime) { super(task, taskQueue, runTime); } @Override public long getScheduleDelay() { if (runTime > Clock.lastKnownForwardProgressingMillis()) { return runTime - Clock.accurateForwardProgressingMillis(); } else { return 0; } } } /** * Implementation of {@link OneTimeTaskWrapper} that provides a schedule delay based off * {@link Clock#lastKnownForwardProgressingMillis()}. * * @since 5.41 */ protected static class GuessOneTimeTaskWrapper extends OneTimeTaskWrapper { public GuessOneTimeTaskWrapper(Runnable task, Queue taskQueue, long runTime) { super(task, taskQueue, runTime); } @Override public long getScheduleDelay() { return runTime - Clock.lastKnownForwardProgressingMillis(); } } /** * Similar to {@link OneTimeTaskWrapper} except that this task must always be eligible for * execution immediately. This allows for some minor assumptions to be made to facilitate * performance. * * @since 5.26 */ protected static class ImmediateTaskWrapper extends OneTimeTaskWrapper { public ImmediateTaskWrapper(Runnable task, Queue taskQueue) { super(task, taskQueue, Clock.lastKnownForwardProgressingMillis()); } @Override public long getScheduleDelay() { // override to avoid volatile read performance hit return 0; } } /** * Abstract wrapper for any tasks which run repeatedly. * * @since 3.1.0 */ protected abstract static class RecurringTaskWrapper extends TaskWrapper { protected final QueueSet queueSet; protected volatile boolean executing; // improves performance compared to executeFlipCounter % 2 == 1 protected long nextRunTime; // executeFlipCounter is used to prevent multiple executions when consumed concurrently // only changed when queue is locked...overflow is fine private volatile short executeFlipCounter; public RecurringTaskWrapper(Runnable task, QueueSet queueSet, long firstRunTime) { super(task); this.queueSet = queueSet; executing = false; this.nextRunTime = firstRunTime; executeFlipCounter = 0; } @Override public long getPureRunTime() { return nextRunTime; } @Override public long getRunTime() { if (executing) { return Long.MAX_VALUE; } else { return nextRunTime; } } @Override public short getExecuteReference() { return executeFlipCounter; } @Override public boolean canExecute(short executeReference) { if (executing | executeFlipCounter != executeReference) { return false; } synchronized (queueSet.scheduleQueue.getModificationLock()) { if (executing | executeFlipCounter != executeReference) { // this task is already running, or not ready to run, so ignore return false; } else { /* we have to reposition to the end atomically so that this task can be removed if * requested to be removed. We can put it at the end because we know this task wont * run again till it has finished (which it will be inserted at the correct point in * queue then. */ int sourceIndex = queueSet.scheduleQueue.indexOf(this); if (sourceIndex >= 0) { if (sourceIndex < queueSet.scheduleQueue.size() - 1 && queueSet.scheduleQueue.get(sourceIndex + 1).getRunTime() != Long.MAX_VALUE) { queueSet.scheduleQueue.reposition(sourceIndex, queueSet.scheduleQueue.size()); } executing = true; executeFlipCounter++; return true; } else { return false; } } } } /** * Call to find and reposition a scheduled task. It is expected that the task provided has * already been added to the queue. This call will use * {@link RecurringTaskWrapper#getRunTime()} to figure out what the new position within the * queue should be. */ protected void reschedule() { int insertionIndex = -1; synchronized (queueSet.scheduleQueue.getModificationLock()) { int currentIndex = queueSet.scheduleQueue.lastIndexOf(this); if (currentIndex > 0) { insertionIndex = SortUtils.getInsertionEndIndex(queueSet.scheduleQueueRunTimeByIndex, queueSet.scheduleQueue.size() - 1, nextRunTime, true); queueSet.scheduleQueue.reposition(currentIndex, insertionIndex); } else if (currentIndex == 0) { insertionIndex = 0; } else { // task removed, no-op, but might as well tidy up the state even though nothing cares } // we can only update executing AFTER the reposition has finished // The synchronization lock must be held during this because changing executing // changes the scheduled delay, and thus we can not have other threads examining the task queue executing = false; executeFlipCounter++; // increment again to indicate execute state change } // kind of awkward we need to know here, but we we need to let the queue set know if the head changed if (insertionIndex == 0) { queueSet.queueListener.handleQueueUpdate(); } } /** * Called when the implementing class should update the variable {@code nextRunTime} to be the * next absolute time in milliseconds the task should run. */ protected abstract void updateNextRunTime(); @Override public void runTask() { if (invalidated) { return; } try { task.run(); } catch (Throwable t) { ExceptionUtils.handleException(t); } if (! invalidated) { updateNextRunTime(); // now that nextRunTime has been set, resort the queue (ask reschedule) reschedule(); // this will set executing to false atomically with the resort } } } /** * Container for tasks which run with a fixed delay after the previous run. This implementation * will provide an accurate {@link #getScheduleDelay()}. * * @since 5.41 */ protected static class AccurateRecurringDelayTaskWrapper extends RecurringTaskWrapper { protected final long recurringDelay; public AccurateRecurringDelayTaskWrapper(Runnable task, QueueSet queueSet, long firstRunTime, long recurringDelay) { super(task, queueSet, firstRunTime); this.recurringDelay = recurringDelay; } @Override protected void updateNextRunTime() { nextRunTime = Clock.accurateForwardProgressingMillis() + recurringDelay; } @Override public long getScheduleDelay() { if (executing) { // this would only be likely if two threads were trying to run the same task return Long.MAX_VALUE; } else if (nextRunTime > Clock.lastKnownForwardProgressingMillis()) { return nextRunTime - Clock.accurateForwardProgressingMillis(); } else { return 0; } } } /** * Container for tasks which run with a fixed delay after the previous run. This implementation * provides a schedule delay based off {@link Clock#lastKnownForwardProgressingMillis()}. * * @since 5.41 */ protected static class GuessRecurringDelayTaskWrapper extends RecurringTaskWrapper { protected final long recurringDelay; public GuessRecurringDelayTaskWrapper(Runnable task, QueueSet queueSet, long firstRunTime, long recurringDelay) { super(task, queueSet, firstRunTime); this.recurringDelay = recurringDelay; } @Override protected void updateNextRunTime() { nextRunTime = Clock.accurateForwardProgressingMillis() + recurringDelay; } @Override public long getScheduleDelay() { if (executing) { // this would only be likely if two threads were trying to run the same task return Long.MAX_VALUE; } else { return nextRunTime - Clock.lastKnownForwardProgressingMillis(); } } } /** * Wrapper for tasks which run at a fixed period (regardless of execution duration). This * implementation will provide an accurate {@link #getScheduleDelay()}. * * @since 5.41 */ protected static class AccurateRecurringRateTaskWrapper extends RecurringTaskWrapper { protected final long period; public AccurateRecurringRateTaskWrapper(Runnable task, QueueSet queueSet, long firstRunTime, long period) { super(task, queueSet, firstRunTime); this.period = period; } @Override protected void updateNextRunTime() { nextRunTime += period; } @Override public long getScheduleDelay() { if (executing) { // this would only be likely if two threads were trying to run the same task return Long.MAX_VALUE; } else if (nextRunTime > Clock.lastKnownForwardProgressingMillis()) { return nextRunTime - Clock.accurateForwardProgressingMillis(); } else { return 0; } } } /** * Wrapper for tasks which run at a fixed period (regardless of execution time). This * implementation provides a schedule delay based off * {@link Clock#lastKnownForwardProgressingMillis()}. * * @since 5.41 */ protected static class GuessRecurringRateTaskWrapper extends RecurringTaskWrapper { protected final long period; public GuessRecurringRateTaskWrapper(Runnable task, QueueSet queueSet, long firstRunTime, long period) { super(task, queueSet, firstRunTime); this.period = period; } @Override protected void updateNextRunTime() { nextRunTime += period; } @Override public long getScheduleDelay() { if (executing) { // this would only be likely if two threads were trying to run the same task return Long.MAX_VALUE; } else { return nextRunTime - Clock.lastKnownForwardProgressingMillis(); } } } /** * Small interface so we can determine internal tasks which were not submitted by users. That * way they can be filtered out (for example in draining the queue). * * @since 4.3.0 */ protected interface InternalRunnable extends Runnable { // nothing added here } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy