Use Durations everywhere, drop unused constructors, and add tests.

This commit is contained in:
Jon Chambers
2021-03-05 12:06:26 -05:00
committed by Jon Chambers
parent 1faedd3870
commit af2a8548c3
8 changed files with 56 additions and 32 deletions

View File

@@ -59,7 +59,8 @@ public class CardinalityRateLimiter {
});
if (rateLimitExceeded) {
throw new RateLimitExceededException();
// Using the TTL as the "retry after" time isn't EXACTLY right, but it's a reasonable approximation
throw new RateLimitExceededException(ttl);
}
}

View File

@@ -9,6 +9,7 @@ import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import java.io.IOException;
import java.time.Duration;
public class LeakyBucket {
@@ -48,15 +49,15 @@ public class LeakyBucket {
(int)Math.floor(this.spaceRemaining + (elapsedTime * this.leakRatePerMillis)));
}
public long getMillisUntilSpace(double amount) {
public Duration getTimeUntilSpaceAvailable(int amount) {
int currentSpaceRemaining = getUpdatedSpaceRemaining();
if (currentSpaceRemaining >= amount) {
return 0;
return Duration.ZERO;
} else if (amount > this.bucketSize) {
// This shouldn't happen today but if so we should bubble this to the clients somehow
return -1;
throw new IllegalArgumentException("Requested permits exceed maximum bucket size");
} else {
return (long)Math.ceil(amount - currentSpaceRemaining / this.leakRatePerMillis);
return Duration.ofMillis((long)Math.ceil((double)(amount - currentSpaceRemaining) / this.leakRatePerMillis));
}
}

View File

@@ -13,6 +13,8 @@ import org.whispersystems.textsecuregcm.controllers.RateLimitExceededException;
import org.whispersystems.textsecuregcm.redis.FaultTolerantRedisCluster;
import org.whispersystems.textsecuregcm.util.Constants;
import java.time.Duration;
import static com.codahale.metrics.MetricRegistry.name;
public class LockingRateLimiter extends RateLimiter {
@@ -30,7 +32,7 @@ public class LockingRateLimiter extends RateLimiter {
public void validate(String key, int amount) throws RateLimitExceededException {
if (!acquireLock(key)) {
meter.mark();
throw new RateLimitExceededException("Locked");
throw new RateLimitExceededException("Locked", Duration.ZERO);
}
try {

View File

@@ -19,6 +19,7 @@ import org.whispersystems.textsecuregcm.util.Constants;
import org.whispersystems.textsecuregcm.util.SystemMapper;
import java.io.IOException;
import java.time.Duration;
import static com.codahale.metrics.MetricRegistry.name;
@@ -28,7 +29,7 @@ public class RateLimiter {
private final ObjectMapper mapper = SystemMapper.getMapper();
private final Meter meter;
protected final Timer validateTimer;
private final Timer validateTimer;
protected final FaultTolerantRedisCluster cacheCluster;
protected final String name;
private final int bucketSize;
@@ -66,7 +67,7 @@ public class RateLimiter {
setBucket(key, bucket);
} else {
meter.mark();
throw new RateLimitExceededException(key + " , " + amount, bucket.getMillisUntilSpace(amount));
throw new RateLimitExceededException(key + " , " + amount, bucket.getTimeUntilSpaceAvailable(amount));
}
}
}
@@ -87,7 +88,7 @@ public class RateLimiter {
return leakRatePerMinute;
}
protected void setBucket(String key, LeakyBucket bucket) {
private void setBucket(String key, LeakyBucket bucket) {
try {
final String serialized = bucket.serialize(mapper);
@@ -97,7 +98,7 @@ public class RateLimiter {
}
}
protected LeakyBucket getBucket(String key) {
private LeakyBucket getBucket(String key) {
try {
final String serialized = cacheCluster.withCluster(connection -> connection.sync().get(getBucketName(key)));