@Override public void execute(Runnable task) { execute(1, task); }
/** * This call will check how far out we have already scheduled tasks to be run. Because it is * the applications responsibility to not provide tasks too fast for the limiter to run them, * this can give an idea of how backed up tasks provided through this limiter actually are. * * @param taskKey object key where {@code equals()} will be used to determine execution thread * @return minimum delay in milliseconds for the next task to be provided */ public int getMinimumDelay(Object taskKey) { RateLimiterExecutor rle = currentLimiters.get(taskKey); if (rle == null) { return 0; } else { return rle.getMinimumDelay(); } }
/** * Exact same as the submit counter part, except you can specify how many permits this task will * require/use (instead of defaulting to 1). The task will be scheduled out as far as necessary * to ensure it conforms to the set rate. * * @param permits resource permits for this task * @param task Runnable to execute when ready * @return Future that will indicate when the execution of this task has completed */ public ListenableFuture<?> submit(double permits, Runnable task) { return submit(permits, task, null); }
@Test (expected = RejectedExecutionException.class) public void rejectDueToScheduleDelay() { limiter = new RateLimiterExecutor(scheduler, 1, 1000); limiter.execute(2000, DoNothingRunnable.instance()); limiter.execute(DoNothingRunnable.instance()); }
@Test public void getCurrentMinimumDelayTest() { assertEquals(0, limiter.getMinimumDelay()); limiter.execute(10, DoNothingRunnable.instance()); int delay = limiter.getMinimumDelay(); assertEquals(10000, delay, 1000); limiter.execute(10, DoNothingRunnable.instance()); delay = limiter.getMinimumDelay(); assertEquals(20000, delay, 1000); }
/** * Exact same as execute counter part, except you can specify how many permits this task will * require/use (instead of defaulting to 1). The task will be scheduled out as far as necessary * to ensure it conforms to the set rate. * * @param permits resource permits for this task * @param task Runnable to execute when ready * @return Time in milliseconds task was delayed to maintain rate, or {@code -1} if rejected but handler did not throw */ public long execute(double permits, Runnable task) { ArgumentVerifier.assertNotNull(task, "task"); ArgumentVerifier.assertNotNegative(permits, "permits"); if (task == DoNothingRunnable.instance()) { long result = taskDelayForPermits(permits); if (result < 0) { rejectedExecutionHandler.handleRejectedTask(task); } return result; } else { return doExecute(permits, task); } }
PriorityScheduler pse = new StrictPriorityScheduler(32); try { RateLimiterExecutor rls = new RateLimiterExecutor(pse, rateLimit); ListenableFuture<?> lastFuture = null; double startTime = Clock.accurateForwardProgressingMillis(); final int permit = (i % 4) + 1; if (i % 2 == 0) { lastFuture = rls.submit(permit, new Runnable() { @Override public void run() { lastFuture = rls.submit(permit, new Callable<Void>() { @Override public Void call() {
@Override public SubmitterExecutor makeSubmitterExecutor(int poolSize, boolean prestartIfAvailable) { return new RateLimiterExecutor(schedulerFactory.makeSchedulerService(poolSize, prestartIfAvailable), rateLimit); }
/** * Constructs a new {@link RateLimiterExecutor}. Tasks will be scheduled on the provided * scheduler, so it is assumed that the scheduler will have enough threads to handle the * average permit amount per task, per second. * <p> * This constructor accepts a maximum schedule delay. If a task requires being scheduled out * beyond this delay, then the provided {@link RejectedExecutionHandler} will be invoked. * * @since 4.8.0 * @param scheduler scheduler to schedule/execute tasks on * @param permitsPerSecond how many permits should be allowed per second * @param maxScheduleDelayMillis Maximum amount of time delay tasks in order to maintain rate * @param rejectedExecutionHandler Handler to accept tasks which could not be executed */ public RateLimiterExecutor(SubmitterScheduler scheduler, double permitsPerSecond, long maxScheduleDelayMillis, RejectedExecutionHandler rejectedExecutionHandler) { ArgumentVerifier.assertNotNull(scheduler, "scheduler"); this.scheduler = scheduler; if (rejectedExecutionHandler == null) { rejectedExecutionHandler = RejectedExecutionHandler.THROW_REJECTED_EXECUTION_EXCEPTION; } this.rejectedExecutionHandler = rejectedExecutionHandler; this.permitLock = new Object(); this.lastScheduleTime = Clock.lastKnownForwardProgressingMillis(); setPermitsPerSecond(permitsPerSecond); setMaxScheduleDelayMillis(maxScheduleDelayMillis); }
@Test public void getFutureTillDelayTest() { // verify that an empty limiter returns a finished future ListenableFuture<?> f = limiter.getFutureTillDelay(0); assertTrue(f.isDone()); // verify a it works if the limiter has waiting tasks limiter.execute(1, DoNothingRunnable.instance()); f = limiter.getFutureTillDelay(0); assertFalse(f.isDone()); scheduler.advance(1000); assertTrue(f.isDone()); }
/** * Performs the execution by scheduling the task out as necessary. The provided permits will * impact the next execution's schedule time to ensure the given rate. * * @param permits number of permits for this task * @param task Runnable to be executed once rate can be maintained * @return Time in milliseconds task was delayed to maintain rate, or {@code -1} if rejected but handler did not throw */ protected long doExecute(double permits, Runnable task) { long taskDelay = taskDelayForPermits(permits); if (taskDelay < 0) { rejectedExecutionHandler.handleRejectedTask(task); } else { scheduler.schedule(task, taskDelay); } return taskDelay; } }
/** * Exact same as the submit counter part, except you can specify how many permits this task will * require/use (instead of defaulting to 1). The task will be scheduled out as far as necessary * to ensure it conforms to the set rate. * * @param <T> type of result returned from the future * @param permits resource permits for this task * @param task Callable to execute when ready * @return Future that will return the callables provided result when the execution has completed */ public <T> ListenableFuture<T> submit(double permits, Callable<T> task) { ArgumentVerifier.assertNotNull(task, "task"); ArgumentVerifier.assertNotNegative(permits, "permits"); ListenableFutureTask<T> lft = new ListenableFutureTask<>(false, task, this); doExecute(permits, lft); return lft; }
@Override public void run() { Iterator<Map.Entry<Object, RateLimiterExecutor>> it = currentLimiters.entrySet().iterator(); long now = Clock.lastKnownForwardProgressingMillis(); while (it.hasNext()) { Map.Entry<Object, RateLimiterExecutor> e = it.next(); if (now - e.getValue().getLastScheduleTime() > LIMITER_IDLE_TIMEOUT) { // if optimistic check above failed, we must remove in `compute` to ensure // no tasks are being submitted while we are removing the limiter currentLimiters.computeIfPresent(e.getKey(), (k, v) -> { if (now - v.getLastScheduleTime() > LIMITER_IDLE_TIMEOUT) { return null; } else { return v; } }); } } if (! currentLimiters.isEmpty()) { signalToRun(); } } }
/** * Exact same as execute counter part, except you can specify how many permits this task will * require/use (instead of defaulting to 1). The task will be scheduled out as far as necessary * to ensure it conforms to the set rate. * * @param permits resource permits for this task * @param task Runnable to execute when ready * @return Time in milliseconds task was delayed to maintain rate, or {@code -1} if rejected but handler did not throw */ public long execute(double permits, Runnable task) { ArgumentVerifier.assertNotNull(task, "task"); ArgumentVerifier.assertNotNegative(permits, "permits"); if (task == DoNothingRunnable.instance()) { long result = taskDelayForPermits(permits); if (result < 0) { rejectedExecutionHandler.handleRejectedTask(task); } return result; } else { return doExecute(permits, task); } }
@Test @SuppressWarnings("unused") public void constructorFail() { try { new RateLimiterExecutor(null, 10); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } try { new RateLimiterExecutor(scheduler, 0); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } }
/** * Constructs a new {@link RateLimiterExecutor}. Tasks will be scheduled on the provided * scheduler, so it is assumed that the scheduler will have enough threads to handle the * average permit amount per task, per second. * <p> * This constructor accepts a maximum schedule delay. If a task requires being scheduled out * beyond this delay, then the provided {@link RejectedExecutionHandler} will be invoked. * * @since 4.8.0 * @param scheduler scheduler to schedule/execute tasks on * @param permitsPerSecond how many permits should be allowed per second * @param maxScheduleDelayMillis Maximum amount of time delay tasks in order to maintain rate * @param rejectedExecutionHandler Handler to accept tasks which could not be executed */ public RateLimiterExecutor(SubmitterScheduler scheduler, double permitsPerSecond, long maxScheduleDelayMillis, RejectedExecutionHandler rejectedExecutionHandler) { ArgumentVerifier.assertNotNull(scheduler, "scheduler"); this.scheduler = scheduler; if (rejectedExecutionHandler == null) { rejectedExecutionHandler = RejectedExecutionHandler.THROW_REJECTED_EXECUTION_EXCEPTION; } this.rejectedExecutionHandler = rejectedExecutionHandler; this.permitLock = new Object(); this.lastScheduleTime = Clock.lastKnownForwardProgressingMillis(); setPermitsPerSecond(permitsPerSecond); setMaxScheduleDelayMillis(maxScheduleDelayMillis); }
/** * Performs the execution by scheduling the task out as necessary. The provided permits will * impact the next execution's schedule time to ensure the given rate. * * @param permits number of permits for this task * @param task Runnable to be executed once rate can be maintained * @return Time in milliseconds task was delayed to maintain rate, or {@code -1} if rejected but handler did not throw */ protected long doExecute(double permits, Runnable task) { long taskDelay = taskDelayForPermits(permits); if (taskDelay < 0) { rejectedExecutionHandler.handleRejectedTask(task); } else { scheduler.schedule(task, taskDelay); } return taskDelay; } }
/** * Exact same as the submit counter part, except you can specify how many permits this task will * require/use (instead of defaulting to 1). The task will be scheduled out as far as necessary * to ensure it conforms to the set rate. * * @param <T> type of result returned from the future * @param permits resource permits for this task * @param task Callable to execute when ready * @return Future that will return the callables provided result when the execution has completed */ public <T> ListenableFuture<T> submit(double permits, Callable<T> task) { ArgumentVerifier.assertNotNull(task, "task"); ArgumentVerifier.assertNotNegative(permits, "permits"); ListenableFutureTask<T> lft = new ListenableFutureTask<>(false, task, this); doExecute(permits, lft); return lft; }
@Override public void run() { Iterator<Map.Entry<Object, RateLimiterExecutor>> it = currentLimiters.entrySet().iterator(); long now = Clock.lastKnownForwardProgressingMillis(); while (it.hasNext()) { Map.Entry<Object, RateLimiterExecutor> e = it.next(); if (now - e.getValue().getLastScheduleTime() > LIMITER_IDLE_TIMEOUT) { // if optimistic check above failed, we must remove in `compute` to ensure // no tasks are being submitted while we are removing the limiter currentLimiters.computeIfPresent(e.getKey(), (k, v) -> { if (now - v.getLastScheduleTime() > LIMITER_IDLE_TIMEOUT) { return null; } else { return v; } }); } } if (! currentLimiters.isEmpty()) { signalToRun(); } } }
@Override public <T> ListenableFuture<T> submit(Runnable task, T result) { return submit(1, task, result); }