I am using Guava RateLimiter and have been creating ratelimiter within my code like shown below.
public class RateLimitedCallable<T> implements Callable<T> {
@Override
public T call() {
Boolean permitAcquired = RateLimitTest.rateLimiter.tryAcquire(1, 1000,
TimeUnit.MILLISECONDS);
if (permitAcquired) {
// do stuff
} else {
throw new RuntimeException("Permit was not granted by RateLimiter");
}
}
}
public class RateLimitTest {
public static final RateLimiter rateLimiter = RateLimiter.create(1.0);
public void test() {
RateLimitedCallable<String> callable = new RateLimitedCallable<>();
callable.call();
callable.call();
callable.call();
callable.call();
}
public static void main(String[] args) {
RateLimitTest limiterTest = new RateLimitTest();
limiterTest.test();
}
}
RuntimeException never gets thrown. However, if I change the timeout to values below 1000 ms, for eg: -
Boolean permitAcquired = RateLimitTest.rateLimiter.tryAcquire(1, 900, TimeUnit.MILLISECONDS);
I do see the RunTimeException, which means the ratelimiter works as expected. I don't understand why the ratelimiter doesn't enforce limits when timeout period is greater than equal to 1000ms. Am I doing something wrong?
First, it's good to remember what tryAcquire
does (emphasis mine):
Acquires the given number of permits from this
RateLimiter
if it can be obtained without exceeding the specifiedtimeout
, or returnsfalse
immediately (without waiting) if the permits would not have been granted before the timeout expired.
In your single thread example, it's normal that it never throws any exception because each call waits roughly one second before it gets the permits. So here's what happens in your code:
Now try using this in a multithreaded example, and you'll start seeing several failures and several successes. Because they all want to acquire the permit at the same time.
So basically, just use a multithreaded environment to test that it's going to happen.
@Test
void test() {
var rateLimiter = RateLimiter.create(1.0);
var stopwatch = Stopwatch.createStarted();
var executor = Executors.newFixedThreadPool(4);
for (var i = 0; i < 4; i++) {
executor.submit(() -> {
if (rateLimiter.tryAcquire(1, 1000, TimeUnit.MILLISECONDS)) {
System.out.printf("Acquired after %s%n", stopwatch);
} else {
System.out.printf("Gave up trying to acquire after %s%n", stopwatch);
}
});
}
executor.shutdown();
try {
if (!executor.awaitTermination(5000, TimeUnit.MILLISECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
}
}
This results in
Acquired after 12.76 ms
Gave up trying to acquire after 12.41 ms
Gave up trying to acquire after 12.43 ms
Acquired after 1.004 s