package org.threadly.concurrent.wrapper.limiter; import java.util.concurrent.RejectedExecutionException; import java.util.concurrent.atomic.AtomicInteger; import org.threadly.concurrent.AbstractSubmitterScheduler; import org.threadly.concurrent.SubmitterScheduler; import org.threadly.concurrent.wrapper.limiter.ExecutorQueueLimitRejector.DecrementingRunnable; import org.threadly.util.ArgumentVerifier; /** * A simple way to limit any {@link SubmitterScheduler} so that queues are managed. In addition * this queue is tracked completely independent of the {@link SubmitterScheduler}'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 SubmitterScheduler}. * <p> * Once the limit has been reached, if additional tasks are supplied the rejected execution * handler will be invoked with the rejected tasks (which by default will throw a * {@link RejectedExecutionException}). This is the threadly equivalent of supplying a limited * sized blocking queue to a java.util.concurrent thread pool. * * See {@link ExecutorQueueLimitRejector}, {@link SchedulerServiceQueueLimitRejector} and * {@link PrioritySchedulerServiceQueueLimitRejector} as other possible implementations. * * @since 4.6.0 (since 4.3.0 at org.threadly.concurrent.limiter) */ public class SubmitterSchedulerQueueLimitRejector extends AbstractSubmitterScheduler { protected final SubmitterScheduler parentScheduler; protected final RejectedExecutionHandler rejectedExecutionHandler; protected final AtomicInteger queuedTaskCount; private int queuedTaskLimit; /** * Constructs a new {@link SubmitterSchedulerQueueLimitRejector} 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 SubmitterSchedulerQueueLimitRejector(SubmitterScheduler parentScheduler, int queuedTaskLimit) { this(parentScheduler, queuedTaskLimit, null); } /** * Constructs a new {@link SubmitterSchedulerQueueLimitRejector} with the provided scheduler and limit. * * @since 4.8.0 * @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 SubmitterSchedulerQueueLimitRejector(SubmitterScheduler parentScheduler, int queuedTaskLimit, RejectedExecutionHandler rejectedExecutionHandler) { ArgumentVerifier.assertNotNull(parentScheduler, "parentExecutor"); this.parentScheduler = parentScheduler; if (rejectedExecutionHandler == null) { rejectedExecutionHandler = RejectedExecutionHandler.THROW_REJECTED_EXECUTION_EXCEPTION; } this.rejectedExecutionHandler = rejectedExecutionHandler; this.queuedTaskCount = new AtomicInteger(); this.queuedTaskLimit = queuedTaskLimit; } @Override public void scheduleWithFixedDelay(Runnable task, long initialDelay, long recurringDelay) { // we don't track recurring tasks parentScheduler.scheduleWithFixedDelay(task, initialDelay, recurringDelay); } @Override public void scheduleAtFixedRate(Runnable task, long initialDelay, long period) { // we don't track recurring tasks parentScheduler.scheduleAtFixedRate(task, initialDelay, period); } /** * Invoked to check how many tasks are currently being tracked as queued by this limiter. * * @return Number of tracked tasks waiting for execution to start */ public int getQueuedTaskCount() { return queuedTaskCount.get(); } /** * Invoked to check the currently set queue limit. * * @return Maximum number of tasks allowed to queue in the parent executor */ public int getQueueLimit() { return queuedTaskLimit; } /** * Invoked to change the set limit. Negative and zero limits are allowed, but they will cause * all executions to be rejected. If set below the current queue size, those tasks will still * remain queued for execution. * * @param newLimit New limit to avoid executions */ public void setQueueLimit(int newLimit) { this.queuedTaskLimit = newLimit; } @Override protected void doSchedule(Runnable task, long delayInMillis) { while (true) { int casValue = queuedTaskCount.get(); if (casValue >= queuedTaskLimit) { 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); } catch (RejectedExecutionException e) { queuedTaskCount.decrementAndGet(); throw e; } break; } // else loop and retry } } }