package org.threadly.concurrent.wrapper.limiter; import java.util.concurrent.Callable; import org.threadly.concurrent.AbstractSubmitterScheduler; import org.threadly.concurrent.RunnableCallableAdapter; import org.threadly.concurrent.SubmitterScheduler; import org.threadly.concurrent.future.ListenableFuture; import org.threadly.concurrent.future.ListenableFutureTask; import org.threadly.util.ArgumentVerifier; /** * Abstract implementation for scheduled keyed limiters. Unlike the other limiters we can not * extend off of each one, and just add extra functionality. Instead they must extend these * abstract classes. The reason for that being that these types of limiters use the other * limiters, rather than extend functionality off each other. * <p> * This adds scheduling functionality. It must exist in an abstract form because of the need to * type the limiter type for specific functionality in the specific limiter type (casting is ugly, * which would be the alternative). * * @since 4.6.0 (since 4.3.0 at org.threadly.concurrent.limiter) * @param <T> Type of limiter stored internally */ abstract class AbstractKeyedSchedulerLimiter<T extends SubmitterSchedulerLimiter> extends AbstractKeyedLimiter<T> { protected final SubmitterScheduler scheduler; protected AbstractKeyedSchedulerLimiter(SubmitterScheduler scheduler, int maxConcurrency, String subPoolName, boolean addKeyToThreadName, int expectedTaskAdditionParallism) { super(scheduler, maxConcurrency, subPoolName, addKeyToThreadName, expectedTaskAdditionParallism); this.scheduler = scheduler; } /** * * Schedule a task with a given delay. There is a slight increase in load when using * {@link #submitScheduled(Object, Runnable, long)} over * {@link #schedule(Object, Runnable, long)}. So this should only be used when the future is * necessary. * <p> * The {@link ListenableFuture#get()} method will return {@code null} once the runnable has * completed. * <p> * The key is used to identify this threads execution limit. Tasks with matching keys will be * limited concurrent execution to the level returned by {@link #getMaxConcurrencyPerKey()}. * <p> * See also: {@link SubmitterScheduler#submitScheduled(Runnable, long)} * * @param taskKey Key to use for identifying execution limit * @param task runnable to execute * @param delayInMs time in milliseconds to wait to execute task * @return a future to know when the task has completed */ public ListenableFuture<?> submitScheduled(Object taskKey, Runnable task, long delayInMs) { return submitScheduled(taskKey, task, null, delayInMs); } /** * Schedule a task with a given delay. The {@link ListenableFuture#get()} method will return * the provided result once the runnable has completed. * <p> * The key is used to identify this threads execution limit. Tasks with matching keys will be * limited concurrent execution to the level returned by {@link #getMaxConcurrencyPerKey()}. * <p> * See also: {@link SubmitterScheduler#submitScheduled(Runnable, Object, long)} * * @param <TT> type of result returned from the future * @param taskKey Key to use for identifying execution limit * @param task runnable to execute * @param result result to be returned from resulting future .get() when runnable completes * @param delayInMs time in milliseconds to wait to execute task * @return a future to know when the task has completed */ public <TT> ListenableFuture<TT> submitScheduled(Object taskKey, Runnable task, TT result, long delayInMs) { return submitScheduled(taskKey, new RunnableCallableAdapter<>(task, result), delayInMs); } /** * Schedule a {@link Callable} with a given delay. This is needed when a result needs to be * consumed from the callable. * <p> * The key is used to identify this threads execution limit. Tasks with matching keys will be * limited concurrent execution to the level returned by {@link #getMaxConcurrencyPerKey()}. * <p> * See also: {@link SubmitterScheduler#submitScheduled(Callable, long)} * * @param <TT> type of result returned from the future * @param taskKey Key to use for identifying execution limit * @param task callable to be executed * @param delayInMs time in milliseconds to wait to execute task * @return a future to know when the task has completed and get the result of the callable */ public <TT> ListenableFuture<TT> submitScheduled(Object taskKey, Callable<TT> task, long delayInMs) { ArgumentVerifier.assertNotNull(task, "task"); ListenableFutureTask<TT> ft = new ListenableFutureTask<>(false, task); schedule(taskKey, ft, delayInMs); return ft; } /** * Schedule a one time task with a given delay. * <p> * The key is used to identify this threads execution limit. Tasks with matching keys will be * limited concurrent execution to the level returned by {@link #getMaxConcurrencyPerKey()}. * <p> * See also: {@link SubmitterScheduler#schedule(Runnable, long)} * * @param taskKey Key to use for identifying execution limit * @param task runnable to execute * @param delayInMs time in milliseconds to wait to execute task */ public void schedule(Object taskKey, Runnable task, long delayInMs) { ArgumentVerifier.assertNotNull(taskKey, "taskKey"); LimiterContainer lc = getLimiterContainer(taskKey); lc.limiter.doSchedule(lc.wrap(task), delayInMs); } /** * Schedule a fixed delay recurring task to run. The recurring delay time will be from the * point where execution has finished. So the execution frequency is the * {@code recurringDelay + runtime} for the provided task. * <p> * Unlike {@link java.util.concurrent.ScheduledExecutorService} if the task throws an exception, * subsequent executions are NOT suppressed or prevented. So if the task throws an exception on * every run, the task will continue to be executed at the provided recurring delay (possibly * throwing an exception on each execution). * <p> * The key is used to identify this threads execution limit. Tasks with matching keys will be * limited concurrent execution to the level returned by {@link #getMaxConcurrencyPerKey()}. * <p> * See also: {@link SubmitterScheduler#scheduleWithFixedDelay(Runnable, long, long)} * * @param taskKey Key to use for identifying execution limit * @param task runnable to be executed * @param initialDelay delay in milliseconds until first run * @param recurringDelay delay in milliseconds for running task after last finish */ public void scheduleWithFixedDelay(Object taskKey, Runnable task, long initialDelay, long recurringDelay) { ArgumentVerifier.assertNotNull(taskKey, "taskKey"); LimiterContainer lc = getLimiterContainer(taskKey); // we don't wrap the task here because it is recurring, this limiter can never be removed lc.limiter.scheduleWithFixedDelay(task, initialDelay, recurringDelay); } /** * Schedule a fixed rate recurring task to run. The recurring delay will be the same, * regardless of how long task execution takes. A given runnable will not run concurrently * (unless it is submitted to the scheduler multiple times). Instead of execution takes longer * than the period, the next run will occur immediately (given thread availability in the pool). * <p> * Unlike {@link java.util.concurrent.ScheduledExecutorService} if the task throws an exception, * subsequent executions are NOT suppressed or prevented. So if the task throws an exception on * every run, the task will continue to be executed at the provided recurring delay (possibly * throwing an exception on each execution). * <p> * The key is used to identify this threads execution limit. Tasks with matching keys will be * limited concurrent execution to the level returned by {@link #getMaxConcurrencyPerKey()}. * <p> * See also: {@link SubmitterScheduler#scheduleAtFixedRate(Runnable, long, long)} * * @param taskKey Key to use for identifying execution limit * @param task runnable to be executed * @param initialDelay delay in milliseconds until first run * @param period amount of time in milliseconds between the start of recurring executions */ public void scheduleAtFixedRate(Object taskKey, Runnable task, long initialDelay, long period) { ArgumentVerifier.assertNotNull(taskKey, "taskKey"); LimiterContainer lc = getLimiterContainer(taskKey); // we don't wrap the task here because it is recurring, this limiter can never be removed lc.limiter.scheduleAtFixedRate(task, initialDelay, period); } /** * Returns a scheduler implementation where all tasks submitted on this scheduler will run on * the provided key. Tasks executed on the returned scheduler will be limited by the key * submitted on this instance equally with ones provided through the returned instance. * * @param taskKey object key where {@code equals()} will be used to determine execution thread * @return scheduler which will only execute with reference to the provided key */ public SubmitterScheduler getSubmitterSchedulerForKey(Object taskKey) { ArgumentVerifier.assertNotNull(taskKey, "taskKey"); return new KeyedSubmitterScheduler(taskKey); } /** * Submitter scheduler which delegates to this instance with a constructed permits and task key. * * @since 4.9.0 */ protected class KeyedSubmitterScheduler extends AbstractSubmitterScheduler { protected final Object taskKey; protected KeyedSubmitterScheduler(Object taskKey) { this.taskKey = taskKey; } @Override public void scheduleWithFixedDelay(Runnable task, long initialDelay, long recurringDelay) { AbstractKeyedSchedulerLimiter.this.scheduleWithFixedDelay(taskKey, task, initialDelay, recurringDelay); } @Override public void scheduleAtFixedRate(Runnable task, long initialDelay, long period) { AbstractKeyedSchedulerLimiter.this.scheduleAtFixedRate(taskKey, task, initialDelay, period); } @Override protected void doSchedule(Runnable task, long delayInMs) { AbstractKeyedSchedulerLimiter.this.schedule(taskKey, task, delayInMs); } } }