Skip to content

Commit d6cc72a

Browse files
RedisSlidingWindowRateLimiter now supports permitCount parameter
1 parent a77709a commit d6cc72a

File tree

3 files changed

+37
-8
lines changed

3 files changed

+37
-8
lines changed

src/RedisRateLimiting/SlidingWindow/RedisSlidingWindowManager.cs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,24 @@ internal class RedisSlidingWindowManager
1414
@"local limit = tonumber(@permit_limit)
1515
local timestamp = tonumber(@current_time)
1616
local window = tonumber(@window)
17+
local requested = tonumber(@permit_count)
18+
19+
local zaddparams = {}
20+
for i=1,requested do
21+
local index = i*2
22+
zaddparams[index-1]=timestamp
23+
zaddparams[index]=@unique_id..':'..tostring(i)
24+
end
1725
1826
-- remove all requests outside current window
1927
redis.call(""zremrangebyscore"", @rate_limit_key, '-inf', timestamp - window)
2028
2129
local count = redis.call(""zcard"", @rate_limit_key)
22-
local allowed = count < limit
30+
local allowed = count + requested <= limit
2331
2432
if allowed
2533
then
26-
redis.call(""zadd"", @rate_limit_key, timestamp, @unique_id)
34+
redis.call(""zadd"", @rate_limit_key, unpack(zaddparams))
2735
end
2836
2937
redis.call(""expireat"", @rate_limit_key, timestamp + window + 1)
@@ -40,7 +48,7 @@ public RedisSlidingWindowManager(
4048
RateLimitKey = new RedisKey($"rl:{{{partitionKey}}}");
4149
}
4250

43-
internal async Task<RedisSlidingWindowResponse> TryAcquireLeaseAsync(string requestId)
51+
internal async Task<RedisSlidingWindowResponse> TryAcquireLeaseAsync(string requestId, int permitCount)
4452
{
4553
var now = DateTimeOffset.UtcNow;
4654
var nowUnixTimeSeconds = now.ToUnixTimeSeconds();
@@ -56,6 +64,7 @@ internal async Task<RedisSlidingWindowResponse> TryAcquireLeaseAsync(string requ
5664
window = _options.Window.TotalSeconds,
5765
current_time = nowUnixTimeSeconds,
5866
unique_id = requestId,
67+
permit_count = permitCount
5968
});
6069

6170
var result = new RedisSlidingWindowResponse();
@@ -69,7 +78,7 @@ internal async Task<RedisSlidingWindowResponse> TryAcquireLeaseAsync(string requ
6978
return result;
7079
}
7180

72-
internal RedisSlidingWindowResponse TryAcquireLease(string requestId)
81+
internal RedisSlidingWindowResponse TryAcquireLease(string requestId, int permitCount)
7382
{
7483
var now = DateTimeOffset.UtcNow;
7584
var nowUnixTimeSeconds = now.ToUnixTimeSeconds();
@@ -85,6 +94,7 @@ internal RedisSlidingWindowResponse TryAcquireLease(string requestId)
8594
window = _options.Window.TotalSeconds,
8695
current_time = nowUnixTimeSeconds,
8796
unique_id = requestId,
97+
permit_count = permitCount
8898
});
8999

90100
var result = new RedisSlidingWindowResponse();

src/RedisRateLimiting/SlidingWindow/RedisSlidingWindowRateLimiter.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ protected override ValueTask<RateLimitLease> AcquireAsyncCore(int permitCount, C
5555
throw new ArgumentOutOfRangeException(nameof(permitCount), permitCount, string.Format("{0} permit(s) exceeds the permit limit of {1}.", permitCount, _options.PermitLimit));
5656
}
5757

58-
return AcquireAsyncCoreInternal();
58+
return AcquireAsyncCoreInternal(permitCount);
5959
}
6060

6161
protected override RateLimitLease AttemptAcquireCore(int permitCount)
@@ -72,7 +72,7 @@ protected override RateLimitLease AttemptAcquireCore(int permitCount)
7272
RequestId = Guid.NewGuid().ToString(),
7373
};
7474

75-
var response = _redisManager.TryAcquireLease(leaseContext.RequestId);
75+
var response = _redisManager.TryAcquireLease(leaseContext.RequestId, permitCount);
7676

7777
leaseContext.Count = response.Count;
7878
leaseContext.Allowed = response.Allowed;
@@ -85,7 +85,7 @@ protected override RateLimitLease AttemptAcquireCore(int permitCount)
8585
return new SlidingWindowLease(isAcquired: false, leaseContext);
8686
}
8787

88-
private async ValueTask<RateLimitLease> AcquireAsyncCoreInternal()
88+
private async ValueTask<RateLimitLease> AcquireAsyncCoreInternal(int permitCount)
8989
{
9090
var leaseContext = new SlidingWindowLeaseContext
9191
{
@@ -94,7 +94,7 @@ private async ValueTask<RateLimitLease> AcquireAsyncCoreInternal()
9494
RequestId = Guid.NewGuid().ToString(),
9595
};
9696

97-
var response = await _redisManager.TryAcquireLeaseAsync(leaseContext.RequestId);
97+
var response = await _redisManager.TryAcquireLeaseAsync(leaseContext.RequestId, permitCount);
9898

9999
leaseContext.Count = response.Count;
100100
leaseContext.Allowed = response.Allowed;

test/RedisRateLimiting.Tests/UnitTests/SlidingWindowUnitTests.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,5 +93,24 @@ public async Task CanAcquireAsyncResource()
9393
using var lease2 = await limiter.AcquireAsync();
9494
Assert.False(lease2.IsAcquired);
9595
}
96+
97+
[Fact]
98+
public async Task SupportsPermitCountFlag()
99+
{
100+
using var limiter = new RedisSlidingWindowRateLimiter<string>(
101+
"Test_SupportsPermitCountFlag_SW",
102+
new RedisSlidingWindowRateLimiterOptions
103+
{
104+
PermitLimit = 3,
105+
Window = TimeSpan.FromMinutes(1),
106+
ConnectionMultiplexerFactory = Fixture.ConnectionMultiplexerFactory,
107+
});
108+
109+
using var lease = await limiter.AcquireAsync(permitCount: 3);
110+
Assert.True(lease.IsAcquired);
111+
112+
using var lease2 = await limiter.AcquireAsync(permitCount: 1);
113+
Assert.False(lease2.IsAcquired);
114+
}
96115
}
97116
}

0 commit comments

Comments
 (0)