Skip to content

Commit 7b4bc04

Browse files
RedisSlidingWindowRateLimiter now supports permitCount parameter
1 parent 2e84899 commit 7b4bc04

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
@@ -16,16 +16,24 @@ internal class RedisSlidingWindowManager
1616
@"local limit = tonumber(@permit_limit)
1717
local timestamp = tonumber(@current_time)
1818
local window = tonumber(@window)
19+
local requested = tonumber(@permit_count)
20+
21+
local zaddparams = {}
22+
for i=1,requested do
23+
local index = i*2
24+
zaddparams[index-1]=timestamp
25+
zaddparams[index]=@unique_id..':'..tostring(i)
26+
end
1927
2028
-- remove all requests outside current window
2129
redis.call(""zremrangebyscore"", @rate_limit_key, '-inf', timestamp - window)
2230
2331
local count = redis.call(""zcard"", @rate_limit_key)
24-
local allowed = count < limit
32+
local allowed = count + requested <= limit
2533
2634
if allowed
2735
then
28-
redis.call(""zadd"", @rate_limit_key, timestamp, @unique_id)
36+
redis.call(""zadd"", @rate_limit_key, unpack(zaddparams))
2937
end
3038
3139
redis.call(""expireat"", @rate_limit_key, timestamp + window + 1)
@@ -57,7 +65,7 @@ public RedisSlidingWindowManager(
5765
StatsRateLimitKey = new RedisKey($"rl:{{{partitionKey}}}:stats");
5866
}
5967

60-
internal async Task<RedisSlidingWindowResponse> TryAcquireLeaseAsync(string requestId)
68+
internal async Task<RedisSlidingWindowResponse> TryAcquireLeaseAsync(string requestId, int permitCount)
6169
{
6270
var now = DateTimeOffset.UtcNow;
6371
var nowUnixTimeSeconds = now.ToUnixTimeSeconds();
@@ -74,6 +82,7 @@ internal async Task<RedisSlidingWindowResponse> TryAcquireLeaseAsync(string requ
7482
stats_key = StatsRateLimitKey,
7583
current_time = nowUnixTimeSeconds,
7684
unique_id = requestId,
85+
permit_count = permitCount
7786
});
7887

7988
var result = new RedisSlidingWindowResponse();
@@ -87,7 +96,7 @@ internal async Task<RedisSlidingWindowResponse> TryAcquireLeaseAsync(string requ
8796
return result;
8897
}
8998

90-
internal RedisSlidingWindowResponse TryAcquireLease(string requestId)
99+
internal RedisSlidingWindowResponse TryAcquireLease(string requestId, int permitCount)
91100
{
92101
var now = DateTimeOffset.UtcNow;
93102
var nowUnixTimeSeconds = now.ToUnixTimeSeconds();
@@ -104,6 +113,7 @@ internal RedisSlidingWindowResponse TryAcquireLease(string requestId)
104113
stats_key = StatsRateLimitKey,
105114
current_time = nowUnixTimeSeconds,
106115
unique_id = requestId,
116+
permit_count = permitCount
107117
});
108118

109119
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
@@ -106,5 +106,24 @@ public async Task CanAcquireAsyncResource()
106106
stats = limiter.GetStatistics()!;
107107
Assert.Equal(0, stats.CurrentAvailablePermits);
108108
}
109+
110+
[Fact]
111+
public async Task SupportsPermitCountFlag()
112+
{
113+
using var limiter = new RedisSlidingWindowRateLimiter<string>(
114+
"Test_SupportsPermitCountFlag_SW",
115+
new RedisSlidingWindowRateLimiterOptions
116+
{
117+
PermitLimit = 3,
118+
Window = TimeSpan.FromMinutes(1),
119+
ConnectionMultiplexerFactory = Fixture.ConnectionMultiplexerFactory,
120+
});
121+
122+
using var lease = await limiter.AcquireAsync(permitCount: 3);
123+
Assert.True(lease.IsAcquired);
124+
125+
using var lease2 = await limiter.AcquireAsync(permitCount: 1);
126+
Assert.False(lease2.IsAcquired);
127+
}
109128
}
110129
}

0 commit comments

Comments
 (0)