Source:
public void reservePermissionsUpfront() throws InterruptedException { final int limitForPeriod = 3; final int tasksNum = 9; Duration limitRefreshPeriod = Duration.ofMillis(1000); Duration timeoutDuration = Duration.ofMillis(1200); Duration durationToWait = limitRefreshPeriod.multipliedBy((long) ceil(((double) tasksNum) / limitForPeriod)); RateLimiterConfig config = RateLimiterConfig.custom() .limitForPeriod(limitForPeriod) .limitRefreshPeriod(limitRefreshPeriod) .timeoutDuration(timeoutDuration) .build(); ExecutorService executorService = Executors.newFixedThreadPool(tasksNum); List<Duration> times = synchronizedList(new ArrayList<>(9)); RateLimiter limiter = buildRateLimiter(config); RateLimiter.Metrics metrics = limiter.getMetrics(); waitForRefresh(metrics, config, '$'); LocalDateTime testStart = LocalDateTime.now(); Runnable runnable = RateLimiter.decorateRunnable(limiter, () -> { times.add(Duration.between(testStart, LocalDateTime.now())); }); for (int i = 0; i < tasksNum; i++) { executorService.submit(runnable); } executorService.shutdown(); boolean terminated = executorService.awaitTermination(durationToWait.toMillis(), TimeUnit.MILLISECONDS); then(terminated).isTrue(); ArrayList<Long> runningDeltas = new ArrayList<>(); long previousDuration = times.get(0).toMillis(); for (Duration time : times) { long current = time.toMillis(); long delta = Math.abs(previousDuration - current); runningDeltas.add(delta); System.out.println("" + current + " [" + delta + "]"); previousDuration = current; } then(runningDeltas.get(0)).isZero(); then(runningDeltas.get(1)).isLessThan(20); then(runningDeltas.get(2)).isLessThan(20); then(runningDeltas.get(3)).isBetween(200L, 1000L); then(runningDeltas.get(4)).isLessThan(20); then(runningDeltas.get(5)).isLessThan(20); then(times).hasSize(6); }

Target:
public void reservePermissionsUpfront() throws InterruptedException { final int limitForPeriod = 3; final int tasksNum = 9; Duration limitRefreshPeriod = Duration.ofMillis(1000); Duration timeoutDuration = Duration.ofMillis(1200); Duration durationToWait = limitRefreshPeriod.multipliedBy((long) ceil(((double) tasksNum) / limitForPeriod)); RateLimiterConfig config = RateLimiterConfig.custom() .limitForPeriod(limitForPeriod) .limitRefreshPeriod(limitRefreshPeriod) .timeoutDuration(timeoutDuration) .build(); ExecutorService executorService = Executors.newFixedThreadPool(tasksNum); List<Duration> times = synchronizedList(new ArrayList<>(9)); RateLimiter limiter = buildRateLimiter(config); RateLimiter.Metrics metrics = limiter.getMetrics(); waitForRefresh(metrics, config, '$'); LocalDateTime testStart = LocalDateTime.now(); Runnable runnable = RateLimiter.decorateRunnable(limiter, () -> { times.add(Duration.between(testStart, LocalDateTime.now())); }); for (int i = 0; i < tasksNum; i++) { executorService.submit(runnable); } executorService.shutdown(); boolean terminated = executorService.awaitTermination(durationToWait.toMillis(), TimeUnit.MILLISECONDS); then(terminated).isTrue(); ArrayList<Long> runningDeltas = new ArrayList<>(); long previousDuration = times.get(0).toMillis(); for (Duration time : times) { long current = time.toMillis(); long delta = Math.abs(previousDuration - current); runningDeltas.add(delta); previousDuration = current; } then(runningDeltas.get(0)).isZero(); then(runningDeltas.get(1)).isLessThan(20); then(runningDeltas.get(2)).isLessThan(20); then(runningDeltas.get(3)).isBetween(200L, 1050L); then(runningDeltas.get(4)).isLessThan(20); then(runningDeltas.get(5)).isLessThan(20); then(times).hasSize(6); }