/** * Provide a task to be run with a given thread key. * <p> * See also: {@link SubmitterExecutor#execute(Runnable)} and * {@link RateLimiterExecutor#execute(Runnable)}. * * @param taskKey object key where {@code equals()} will be used to determine execution thread * @param task Task to be executed */ public void execute(Object taskKey, Runnable task) { execute(1, taskKey, task); }
/** * Submit a task to be run with a given thread key. * <p> * See also: {@link SubmitterExecutor#submit(Runnable)} and * {@link RateLimiterExecutor#submit(Runnable)}. * * @param taskKey object key where {@code equals()} will be used to determine execution thread * @param task Task to be executed * @return Future to represent when the execution has occurred */ public ListenableFuture<?> submit(Object taskKey, Runnable task) { return submit(1, taskKey, task, null); }
/** * Returns an executor implementation where all tasks submitted on this executor 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 Executor which will only execute with reference to the provided key */ public SubmitterExecutor getSubmitterExecutorForKey(Object taskKey) { return getSubmitterExecutorForKey(1, taskKey); }
@Test public void getCurrentMinimumDelayTest() { Object key = new Object(); assertEquals(0, limiter.getMinimumDelay(key)); limiter.execute(10, key, DoNothingRunnable.instance()); int delay = limiter.getMinimumDelay(key); assertEquals(10000, delay, 1000); limiter.execute(10, key, DoNothingRunnable.instance()); delay = limiter.getMinimumDelay(key); assertEquals(20000, delay, 1000); }
@Override protected void doExecute(Runnable task) { limiterForKey(taskKey, (l) -> l.execute(permits, task)); } }
@Test (expected = RejectedExecutionException.class) public void rejectDueToScheduleDelay() { limiter = new KeyedRateLimiterExecutor(scheduler, 1, 1000); limiter.execute(2000, "foo", DoNothingRunnable.instance()); limiter.execute("foo", DoNothingRunnable.instance()); }
@Test public void getFutureTillDelayTest() { Object key = new Object(); // verify that an empty limiter returns a finished future ListenableFuture<?> f = limiter.getFutureTillDelay(key, 0); assertTrue(f.isDone()); // verify it works if the limiter has waiting tasks limiter.execute(key, DoNothingRunnable.instance()); f = limiter.getFutureTillDelay(key, 0); assertFalse(f.isDone()); scheduler.advance(1000); assertTrue(f.isDone()); }
@Test public void verifyCleanupTaskTest() { double permits = .1; Object key = new Object(); limiter.execute(permits, key, new TestRunnable()); assertEquals(2, scheduler.advance(KeyedRateLimiterExecutor.LIMITER_IDLE_TIMEOUT)); assertEquals(1, limiter.getTrackedKeyCount()); assertFalse(limiter.currentLimiters.isEmpty()); if (TEST_PROFILE == TestLoad.Stress) { // too slow for normal tests right now TestUtils.sleep((long)(KeyedRateLimiterExecutor.LIMITER_IDLE_TIMEOUT + (1000 * permits))); TestUtils.blockTillClockAdvances(); assertEquals(1, scheduler.advance(KeyedRateLimiterExecutor.LIMITER_IDLE_TIMEOUT)); assertTrue(limiter.currentLimiters.isEmpty()); assertEquals(0, limiter.getTrackedKeyCount()); } }
PriorityScheduler pse = new StrictPriorityScheduler(32); try { KeyedRateLimiterExecutor krls = new KeyedRateLimiterExecutor(pse, rateLimit); ListenableFuture<?> lastFuture = null; double startTime = Clock.accurateForwardProgressingMillis(); final int permit = (i % 4) + 1; if (flip) { lastFuture = krls.submit(permit, key, new Runnable() { @Override public void run() { flip = false; } else { lastFuture = krls.submit(permit, key, new Callable<Void>() { @Override public Void call() {
/** * In order to help assist with avoiding to schedule too much on the scheduler at any given * time, this call returns a future that will block until the delay for the next task falls * below the maximum delay provided into this call. If you want to ensure that the next task * will execute immediately, you should provide a zero to this function. If more tasks are * added to the limiter after this call, it will NOT impact when this future will unblock. So * this future is assuming that nothing else is added to the limiter after requested. * * @param taskKey object key where {@code equals()} will be used to determine execution thread * @param maximumDelay maximum delay in milliseconds until returned Future should unblock * @return Future that will unblock {@code get()} calls once delay has been reduced below the provided maximum */ public ListenableFuture<?> getFutureTillDelay(Object taskKey, long maximumDelay) { int currentMinimumDelay = getMinimumDelay(taskKey); if (currentMinimumDelay == 0) { return ImmediateResultListenableFuture.NULL_RESULT; } else { long futureDelay; if (maximumDelay > 0 && currentMinimumDelay > maximumDelay) { futureDelay = maximumDelay; } else { futureDelay = currentMinimumDelay; } return scheduler.submitScheduled(DoNothingRunnable.instance(), futureDelay); } }
@SuppressWarnings("unused") @Test public void constructorFail() { try { new KeyedRateLimiterExecutor(null, 10); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } try { new KeyedRateLimiterExecutor(scheduler, 0); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } }
@Override protected void doExecute(Runnable task) { limiterForKey(taskKey, (l) -> l.execute(permits, task)); } }
/** * In order to help assist with avoiding to schedule too much on the scheduler at any given * time, this call returns a future that will block until the delay for the next task falls * below the maximum delay provided into this call. If you want to ensure that the next task * will execute immediately, you should provide a zero to this function. If more tasks are * added to the limiter after this call, it will NOT impact when this future will unblock. So * this future is assuming that nothing else is added to the limiter after requested. * * @param taskKey object key where {@code equals()} will be used to determine execution thread * @param maximumDelay maximum delay in milliseconds until returned Future should unblock * @return Future that will unblock {@code get()} calls once delay has been reduced below the provided maximum */ public ListenableFuture<?> getFutureTillDelay(Object taskKey, long maximumDelay) { int currentMinimumDelay = getMinimumDelay(taskKey); if (currentMinimumDelay == 0) { return ImmediateResultListenableFuture.NULL_RESULT; } else { long futureDelay; if (maximumDelay > 0 && currentMinimumDelay > maximumDelay) { futureDelay = maximumDelay; } else { futureDelay = currentMinimumDelay; } return scheduler.submitScheduled(DoNothingRunnable.instance(), futureDelay); } }
/** * Submit a task to be run with a given thread key. * <p> * See also: {@link SubmitterExecutor#submit(Runnable)} and * {@link RateLimiterExecutor#submit(Runnable)}. * * @param taskKey object key where {@code equals()} will be used to determine execution thread * @param task Task to be executed * @return Future to represent when the execution has occurred */ public ListenableFuture<?> submit(Object taskKey, Runnable task) { return submit(1, taskKey, task, null); }
/** * Provide a task to be run with a given thread key. * <p> * See also: {@link SubmitterExecutor#execute(Runnable)} and * {@link RateLimiterExecutor#execute(Runnable)}. * * @param taskKey object key where {@code equals()} will be used to determine execution thread * @param task Task to be executed */ public void execute(Object taskKey, Runnable task) { execute(1, taskKey, task); }
/** * Provide a task to be run with a given thread key. * <p> * See also: {@link SubmitterExecutor#execute(Runnable)} and * {@link RateLimiterExecutor#execute(double, Runnable)}. * * @param permits resource permits for this task * @param taskKey object key where {@code equals()} will be used to determine execution thread * @param task Task to be executed * @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, Object taskKey, Runnable task) { return limiterForKey(taskKey, (l) -> l.execute(permits, task)); }
/** * Returns an executor implementation where all tasks submitted on this executor 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 Executor which will only execute with reference to the provided key */ public SubmitterExecutor getSubmitterExecutorForKey(Object taskKey) { return getSubmitterExecutorForKey(1, taskKey); }
/** * Submit a task to be run with a given thread key. * <p> * See also: {@link SubmitterExecutor#submit(Runnable)} and * {@link RateLimiterExecutor#submit(double, Runnable)}. * * @param permits resource permits for this task * @param taskKey object key where {@code equals()} will be used to determine execution thread * @param task Task to be executed * @return Future to represent when the execution has occurred */ public ListenableFuture<?> submit(double permits, Object taskKey, Runnable task) { return submit(permits, taskKey, task, null); }
@Test public void executeWithPermitsFail() { try { limiter.execute(-1, new Object(), DoNothingRunnable.instance()); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } try { limiter.execute(1, new Object(), null); fail("Exception should have thrown"); } catch (IllegalArgumentException e) { // expected } }
/** * Provide a task to be run with a given thread key. * <p> * See also: {@link SubmitterExecutor#execute(Runnable)} and * {@link RateLimiterExecutor#execute(double, Runnable)}. * * @param permits resource permits for this task * @param taskKey object key where {@code equals()} will be used to determine execution thread * @param task Task to be executed * @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, Object taskKey, Runnable task) { return limiterForKey(taskKey, (l) -> l.execute(permits, task)); }