Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -298,24 +298,44 @@ void rateLimitNoBoundaryDoubling() throws InterruptedException {
ValkeyRateLimitStore store = newRateLimitStore(factoryA);
String key = "boundary-" + java.util.UUID.randomUUID();
long capacity = 5;
Duration window = Duration.ofMillis(500);
// refillGreedy tops the bucket up continuously, one token every window/capacity. A 500ms
// window left the drain loop only 100ms before a 6th token appeared, so a slow Valkey
// round-trip broke the count; 4s spaces refills 800ms apart, clear of any burst.
Duration window = Duration.ofSeconds(4);
long refillIntervalMs = window.toMillis() / capacity;

long drainStart = System.nanoTime();
int firstAllowed = 0;
for (int i = 0; i < 10; i++) {
if (store.tryConsume(key, capacity, window).allowed()) firstAllowed++;
}
assertEquals(capacity, firstAllowed, "must allow exactly capacity tokens initially");

Thread.sleep(window.toMillis() + 50);
long drainMs = (System.nanoTime() - drainStart) / 1_000_000;
// Refill never pauses, so a slow drain earns extra tokens honestly - allow exactly the
// number the elapsed time can have produced and no more.
long earned = drainMs / refillIntervalMs;
assertTrue(
firstAllowed >= capacity && firstAllowed <= capacity + earned,
"initial burst must be capacity ("
+ capacity
+ ") plus at most the "
+ earned
+ " token(s) refilled during a "
+ drainMs
+ "ms drain, got "
+ firstAllowed);

// A fixed-window limiter would hand back a whole fresh capacity at the boundary; a token
// bucket hands back one token per refill interval.
Thread.sleep(refillIntervalMs + 200);
int secondAllowed = 0;
long start = System.nanoTime();
for (int i = 0; i < 20 && (System.nanoTime() - start) < 20_000_000L; i++) {
for (int i = 0; i < 10; i++) {
if (store.tryConsume(key, capacity, window).allowed()) secondAllowed++;
}
assertTrue(
secondAllowed <= capacity,
"token-bucket must not let a fresh full capacity be consumed instantly across"
+ " the boundary; got "
secondAllowed >= 1 && secondAllowed < capacity,
"one refill interval must yield about one token, not a fresh full window of "
+ capacity
+ "; got "
+ secondAllowed);
}

Expand Down
Loading