package org.threadly.concurrent.wrapper.limiter;
import java.util.concurrent.Callable;
import org.threadly.concurrent.RunnableCallableAdapter;
import org.threadly.concurrent.RunnableContainer;
import org.threadly.concurrent.SubmitterScheduler;
import org.threadly.concurrent.future.ListenableFuture;
import org.threadly.concurrent.future.ListenableFutureTask;
import org.threadly.util.ArgumentVerifier;
import org.threadly.util.Clock;
/**
* This class is designed to limit how much parallel execution happens on a provided
* {@link SubmitterScheduler}. This allows the implementor to have one thread pool for all
* their code, and if they want certain sections to have less levels of parallelism (possibly
* because those those sections would completely consume the global pool), they can wrap the
* executor in this class.
* <p>
* Thus providing you better control on the absolute thread count and how much parallelism can
* occur in different sections of the program.
* <p>
* This is an alternative from having to create multiple thread pools. By using this you also are
* able to accomplish more efficiently thread use than multiple thread pools would.
*
* @since 4.6.0 (since 4.3.0 at org.threadly.concurrent.limiter)
*/
public class SubmitterSchedulerLimiter extends ExecutorLimiter implements SubmitterScheduler {
protected final SubmitterScheduler scheduler;
/**
* Constructs a new limiter that implements the {@link SubmitterScheduler}.
*
* @param scheduler {@link SubmitterScheduler} implementation to submit task executions to.
* @param maxConcurrency maximum quantity of runnables to run in parallel
*/
public SubmitterSchedulerLimiter(SubmitterScheduler scheduler, int maxConcurrency) {
super(scheduler, maxConcurrency);
this.scheduler = scheduler;
}
@Override
public ListenableFuture<?> submitScheduled(Runnable task, long delayInMs) {
return submitScheduled(task, null, delayInMs);
}
@Override
public <T> ListenableFuture<T> submitScheduled(Runnable task, T result, long delayInMs) {
return submitScheduled(new RunnableCallableAdapter<>(task, result), delayInMs);
}
@Override
public <T> ListenableFuture<T> submitScheduled(Callable<T> task, long delayInMs) {
ArgumentVerifier.assertNotNull(task, "task");
ListenableFutureTask<T> ft = new ListenableFutureTask<>(false, task);
doSchedule(ft, delayInMs);
return ft;
}
/**
* Adds a task to either execute (delay zero), or schedule with the provided delay. No safety
* checks are done at this point, so only provide non-null inputs.
*
* @param task Task for execution
* @param delayInMs delay in milliseconds, greater than or equal to zero
*/
protected void doSchedule(Runnable task, long delayInMs) {
if (delayInMs == 0) {
doExecute(task);
} else {
scheduler.schedule(new DelayedExecutionRunnable(task), delayInMs);
}
}
@Override
public void schedule(Runnable task, long delayInMs) {
ArgumentVerifier.assertNotNull(task, "task");
ArgumentVerifier.assertNotNegative(delayInMs, "delayInMs");
doSchedule(task, delayInMs);
}
@Override
public void scheduleWithFixedDelay(Runnable task, long initialDelay, long recurringDelay) {
ArgumentVerifier.assertNotNull(task, "task");
ArgumentVerifier.assertNotNegative(initialDelay, "initialDelay");
ArgumentVerifier.assertNotNegative(recurringDelay, "recurringDelay");
doSchedule(new RecurringDelayWrapper(task, recurringDelay), initialDelay);
}
@Override
public void scheduleAtFixedRate(Runnable task, long initialDelay, long period) {
ArgumentVerifier.assertNotNull(task, "task");
ArgumentVerifier.assertNotNegative(initialDelay, "initialDelay");
ArgumentVerifier.assertGreaterThanZero(period, "period");
doSchedule(new RecurringRateWrapper(task, initialDelay, period), initialDelay);
}
/**
* Small runnable that allows scheduled tasks to pass through the same execution queue that
* immediate execution has to.
*
* @since 1.1.0
*/
protected class DelayedExecutionRunnable implements Runnable, RunnableContainer {
private final LimiterRunnableWrapper lrw;
protected DelayedExecutionRunnable(Runnable runnable) {
this(new LimiterRunnableWrapper(executor, runnable));
}
protected DelayedExecutionRunnable(LimiterRunnableWrapper lrw) {
this.lrw = lrw;
}
@Override
public void run() {
if (canRunTask()) { // we can run in the thread we already have
lrw.run();
} else {
addToQueue(lrw);
}
}
@Override
public Runnable getContainedRunnable() {
return lrw.getContainedRunnable();
}
}
/**
* Wrapper for recurring tasks that reschedule with a given delay after completing execution.
*
* @since 3.1.0
*/
protected class RecurringDelayWrapper extends LimiterRunnableWrapper {
protected final long recurringDelay;
protected final DelayedExecutionRunnable delayRunnable;
public RecurringDelayWrapper(Runnable runnable, long recurringDelay) {
super(scheduler, runnable);
this.recurringDelay = recurringDelay;
delayRunnable = new DelayedExecutionRunnable(this);
}
@Override
protected void doAfterRunTasks() {
scheduler.schedule(delayRunnable, recurringDelay);
}
}
/**
* Wrapper for recurring tasks that reschedule at a fixed rate after completing execution.
*
* @since 3.1.0
*/
protected class RecurringRateWrapper extends LimiterRunnableWrapper {
protected final long period;
protected final DelayedExecutionRunnable delayRunnable;
private long nextRunTime;
public RecurringRateWrapper(Runnable runnable, long initialDelay, long period) {
super(scheduler, runnable);
this.period = period;
delayRunnable = new DelayedExecutionRunnable(this);
nextRunTime = Clock.accurateForwardProgressingMillis() + initialDelay + period;
}
@Override
protected void doAfterRunTasks() {
nextRunTime += period;
long nextDelay = nextRunTime - Clock.accurateForwardProgressingMillis();
if (nextDelay < 1) {
executeWrapper(this);
} else {
scheduler.schedule(delayRunnable, nextDelay);
}
}
}
}