Skip to content

Fix availablePermits() returning value greater than rate after release() and expiration - #7334

Open
angus-guo wants to merge 1 commit into
redisson:masterfrom
angus-guo:fix/available-permits-clamp-to-rate
Open

Fix availablePermits() returning value greater than rate after release() and expiration#7334
angus-guo wants to merge 1 commit into
redisson:masterfrom
angus-guo:fix/available-permits-clamp-to-rate

Conversation

@angus-guo

@angus-guo angus-guo commented Aug 30, 2026

Copy link
Copy Markdown

Fixes #7329

Problem

When a permit is acquired, released, and then expires from the sorted set, availablePermitsAsync() adds the expired permit count twice:

  1. release() already restored the permit and clamped currentValue to rate
  2. After the interval elapses, the sorted set entry (which release() did not prune) is counted as expired and added again — without any clamp

This causes availablePermits() to return a value greater than the configured rate, and that over-limit value is persisted back to Redis.

Root cause

availablePermitsAsync() line 592 adds released to currentValue with no rate ceiling:

currentValue = tonumber(currentValue) + released;
redis.call('set', valueName, currentValue);

releaseAsync() (line 303) already has the clamp:

if newValue > tonumber(rate) then
    newValue = tonumber(rate);
end;

Fix

Apply the same clamp in availablePermitsAsync() after adding expired permits but before persisting currentValue.

Testing

The existing testRelease() didn't catch this because it runs fast enough that no permits reach the expiration point (released = 0). Two new tests sleep past the interval to trigger the double-counting path and assert the clamp holds.

Note: I don't have Docker installed locally, so I couldn't run the RedisDockerTest-based tests. The fix has been verified by:

  • Compiling both main and test code without errors
  • Manually tracing the Lua script arithmetic (documented in the issue comment)
  • CI will run the full integration tests

@angus-guo
angus-guo force-pushed the fix/available-permits-clamp-to-rate branch from 607145d to 51a519a Compare August 30, 2026 17:11
likerhythm added a commit to likerhythm/redisson-lab that referenced this pull request Sep 1, 2026
…rmitsAsync() 테스트 버전(배포 버전X)

기존 배포 버전을 사용하고 싶다면 이 클래스를 전체 주석처리하면 됨.

관련 Issue: redisson/redisson#7329
관련 PR: redisson/redisson#7334
@angus-guo
angus-guo force-pushed the fix/available-permits-clamp-to-rate branch from 51a519a to d253420 Compare September 2, 2026 13:16
…e() and expiration

When a permit is released and later expires from the sorted set, availablePermitsAsync
would add the released amount twice: once by release() (which clamps to rate), and again
when the expired entry is counted. The second addition had no clamp, so currentValue
could exceed the configured rate and be returned to the caller and persisted.

releaseAsync() already clamps with 'if newValue > tonumber(rate) then newValue = rate'.
This applies the same clamp to availablePermitsAsync after adding expired permits.

The existing testRelease() passes because it runs so fast that no permits reach the
expiration point; the new regression tests sleep past the interval to trigger the
double-counting path.

Fixes redisson#7329

Signed-off-by: angus-guo <217034332+angus-guo@users.noreply.github.com>
@angus-guo
angus-guo force-pushed the fix/available-permits-clamp-to-rate branch from d253420 to d8e6469 Compare September 7, 2026 16:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Development

Successfully merging this pull request may close these issues.

RedissonRateLimiter.release() causes availablePermits() to return a value greater than the rate.

1 participant