package org.threadly.concurrent.wrapper.limiter;
import java.util.concurrent.Callable;
import java.util.concurrent.RejectedExecutionException;
import org.threadly.concurrent.PrioritySchedulerService;
import org.threadly.concurrent.RunnableCallableAdapter;
import org.threadly.concurrent.TaskPriority;
import org.threadly.concurrent.future.ListenableFuture;
import org.threadly.concurrent.future.ListenableFutureTask;
import org.threadly.concurrent.wrapper.limiter.ExecutorQueueLimitRejector.DecrementingRunnable;
import org.threadly.util.ArgumentVerifier;
/**
* A simple way to limit any {@link PrioritySchedulerService} so that queues are managed. In
* addition this queue is tracked completely independent of the {@link PrioritySchedulerService}'s
* actual queue, so these can be distributed in code to limit queues differently to different parts
* of the system, while letting them all back the same {@link PrioritySchedulerService}.
* <p>
* Once the limit has been reached, if additional tasks are supplied a
* {@link java.util.concurrent.RejectedExecutionException} will be thrown. This is the threadly
* equivalent of supplying a limited sized blocking queue to a java.util.concurrent thread
* pool.
* <p>
* See {@link ExecutorQueueLimitRejector}, {@link SubmitterSchedulerQueueLimitRejector} and
* {@link SchedulerServiceQueueLimitRejector} as other possible implementations.
*
* @since 4.8.0
*/
public class PrioritySchedulerServiceQueueLimitRejector extends SchedulerServiceQueueLimitRejector
implements PrioritySchedulerService {
protected final PrioritySchedulerService parentScheduler;
protected final boolean dontLimitStarvable;
/**
* Constructs a new {@link PrioritySchedulerServiceQueueLimitRejector} with the provided
* scheduler and limit.
*
* @param parentScheduler Scheduler to execute and schedule tasks on to
* @param queuedTaskLimit Maximum number of queued tasks before executions should be rejected
*/
public PrioritySchedulerServiceQueueLimitRejector(PrioritySchedulerService parentScheduler, int queuedTaskLimit) {
this(parentScheduler, queuedTaskLimit, null);
}
/**
* Constructs a new {@link PrioritySchedulerServiceQueueLimitRejector} with the provided
* scheduler, limit, and handler for when task can not be submitted to the pool.
*
* @param parentScheduler Scheduler to execute and schedule tasks on to
* @param queuedTaskLimit Maximum number of queued tasks before executions should be rejected
* @param rejectedExecutionHandler Handler to accept tasks which could not be executed due to queue size
*/
public PrioritySchedulerServiceQueueLimitRejector(PrioritySchedulerService parentScheduler, int queuedTaskLimit,
RejectedExecutionHandler rejectedExecutionHandler) {
this(parentScheduler, queuedTaskLimit, false, rejectedExecutionHandler);
}
/**
* Constructs a new {@link PrioritySchedulerServiceQueueLimitRejector} with the provided
* scheduler and limit. This constructor additionally allows you to specify if starvable tasks
* should be included in the queue limit. Because starvable tasks have less impact on the pool,
* the need to limit them may be reduced.
*
* @param parentScheduler Scheduler to execute and schedule tasks on to
* @param queuedTaskLimit Maximum number of queued tasks before executions should be rejected
* @param dontLimitStarvable Provide {@code true} to don't include starvable tasks against queue limit
*/
public PrioritySchedulerServiceQueueLimitRejector(PrioritySchedulerService parentScheduler,
int queuedTaskLimit, boolean dontLimitStarvable) {
this(parentScheduler, queuedTaskLimit, dontLimitStarvable, null);
}
/**
* Constructs a new {@link PrioritySchedulerServiceQueueLimitRejector} with the provided
* scheduler, limit, and handler for when task can not be submitted to the pool. This
* constructor additionally allows you to specify if starvable tasks should be included in the
* queue limit. Because starvable tasks have less impact on the pool, the need to limit them
* may be reduced.
*
* @param parentScheduler Scheduler to execute and schedule tasks on to
* @param queuedTaskLimit Maximum number of queued tasks before executions should be rejected
* @param dontLimitStarvable Provide {@code true} to don't include starvable tasks against queue limit
* @param rejectedExecutionHandler Handler to accept tasks which could not be executed due to queue size
*/
public PrioritySchedulerServiceQueueLimitRejector(PrioritySchedulerService parentScheduler,
int queuedTaskLimit, boolean dontLimitStarvable,
RejectedExecutionHandler rejectedExecutionHandler) {
super(parentScheduler, queuedTaskLimit, rejectedExecutionHandler);
this.parentScheduler = parentScheduler;
this.dontLimitStarvable = dontLimitStarvable;
}
protected void doSchedule(Runnable task, long delayInMillis, TaskPriority priority) {
if (dontLimitStarvable && priority == TaskPriority.Starvable) {
parentScheduler.schedule(task, delayInMillis, priority);
return;
}
while (true) {
int casValue = queuedTaskCount.get();
if (casValue >= getQueueLimit()) {
rejectedExecutionHandler.handleRejectedTask(task);
return; // in case handler did not throw exception
} else if (queuedTaskCount.compareAndSet(casValue, casValue + 1)) {
try {
parentScheduler.schedule(new DecrementingRunnable(task, queuedTaskCount),
delayInMillis, priority);
} catch (RejectedExecutionException e) {
queuedTaskCount.decrementAndGet();
throw e;
}
break;
} // else loop and retry
}
}
@Override
public void execute(Runnable task, TaskPriority priority) {
ArgumentVerifier.assertNotNull(task, "task");
doSchedule(task, 0, priority);
}
@Override
public ListenableFuture<?> submit(Runnable task, TaskPriority priority) {
return submit(task, null, priority);
}
@Override
public <T> ListenableFuture<T> submit(Runnable task, T result, TaskPriority priority) {
return submit(new RunnableCallableAdapter<T>(task, result), priority);
}
@Override
public <T> ListenableFuture<T> submit(Callable<T> task, TaskPriority priority) {
ArgumentVerifier.assertNotNull(task, "task");
ListenableFutureTask<T> lft = new ListenableFutureTask<T>(false, task);
doSchedule(lft, 0, priority);
return lft;
}
@Override
public void schedule(Runnable task, long delayInMs, TaskPriority priority) {
ArgumentVerifier.assertNotNull(task, "task");
ArgumentVerifier.assertNotNegative(delayInMs, "delayInMs");
doSchedule(task, delayInMs, priority);
}
@Override
public ListenableFuture<?> submitScheduled(Runnable task, long delayInMs, TaskPriority priority) {
return submitScheduled(task, null, delayInMs, priority);
}
@Override
public <T> ListenableFuture<T> submitScheduled(Runnable task, T result, long delayInMs,
TaskPriority priority) {
return submitScheduled(new RunnableCallableAdapter<T>(task, result), delayInMs, priority);
}
@Override
public <T> ListenableFuture<T> submitScheduled(Callable<T> task, long delayInMs,
TaskPriority priority) {
ArgumentVerifier.assertNotNull(task, "task");
ArgumentVerifier.assertNotNegative(delayInMs, "delayInMs");
ListenableFutureTask<T> lft = new ListenableFutureTask<T>(false, task);
doSchedule(lft, delayInMs, priority);
return lft;
}
@Override
public void scheduleWithFixedDelay(Runnable task, long initialDelay, long recurringDelay,
TaskPriority priority) {
// we don't track recurring tasks
parentScheduler.scheduleWithFixedDelay(task, initialDelay, recurringDelay, priority);
}
@Override
public void scheduleAtFixedRate(Runnable task, long initialDelay, long period,
TaskPriority priority) {
// we don't track recurring tasks
parentScheduler.scheduleAtFixedRate(task, initialDelay, period, priority);
}
@Override
public TaskPriority getDefaultPriority() {
return parentScheduler.getDefaultPriority();
}
@Override
public long getMaxWaitForLowPriority() {
return parentScheduler.getMaxWaitForLowPriority();
}
@Override
public int getQueuedTaskCount(TaskPriority priority) {
return parentScheduler.getQueuedTaskCount(priority);
}
}