Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/dotnet/runtime.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorgithub-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>2022-09-03 03:03:47 +0300
committerGitHub <noreply@github.com>2022-09-03 03:03:47 +0300
commit8b5185a5dd56d7d55c61e7373f3b8483a9733602 (patch)
tree6380efa70316f1cfc639f39cef0eb03a4fc302bf /src
parentd2e3961cffdab15659f54f431bef6e96c38e384c (diff)
[release/7.0] [RateLimiting] Handle Timer jitter (#74971)
* [RateLimiting] TryReplenish handles multiple replenish periods at a time * ignore tick on auto * fixup * partial * allow TimeSpan.Zero * no special case * TimeSpan.Zero * Apply suggestions from code review Co-authored-by: Stephen Halter <halter73@gmail.com> Co-authored-by: Brennan Conroy <brecon@microsoft.com> Co-authored-by: Stephen Halter <halter73@gmail.com>
Diffstat (limited to 'src')
-rw-r--r--src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiter.cs17
-rw-r--r--src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiterOptions.cs2
-rw-r--r--src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs10
-rw-r--r--src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiterOptions.cs2
-rw-r--r--src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiter.cs40
-rw-r--r--src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiterOptions.cs2
-rw-r--r--src/libraries/System.Threading.RateLimiting/tests/FixedWindowRateLimiterTests.cs220
-rw-r--r--src/libraries/System.Threading.RateLimiting/tests/SlidingWindowRateLimiterTests.cs313
-rw-r--r--src/libraries/System.Threading.RateLimiting/tests/TokenBucketRateLimiterTests.cs315
9 files changed, 562 insertions, 359 deletions
diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiter.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiter.cs
index fe4b0c29c3a..780eddd2cee 100644
--- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiter.cs
+++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiter.cs
@@ -59,9 +59,9 @@ namespace System.Threading.RateLimiting
{
throw new ArgumentException($"{nameof(options.QueueLimit)} must be set to a value greater than or equal to 0.", nameof(options));
}
- if (options.Window < TimeSpan.Zero)
+ if (options.Window <= TimeSpan.Zero)
{
- throw new ArgumentException($"{nameof(options.Window)} must be set to a value greater than or equal to TimeSpan.Zero.", nameof(options));
+ throw new ArgumentException($"{nameof(options.Window)} must be set to a value greater than TimeSpan.Zero.", nameof(options));
}
_options = new FixedWindowRateLimiterOptions
@@ -287,7 +287,7 @@ namespace System.Threading.RateLimiting
return;
}
- if ((long)((nowTicks - _lastReplenishmentTick) * TickFrequency) < _options.Window.Ticks)
+ if (((nowTicks - _lastReplenishmentTick) * TickFrequency) < _options.Window.Ticks && !_options.AutoReplenishment)
{
return;
}
@@ -295,21 +295,14 @@ namespace System.Threading.RateLimiting
_lastReplenishmentTick = nowTicks;
int availableRequestCounters = _requestCount;
- int maxPermits = _options.PermitLimit;
- int resourcesToAdd;
- if (availableRequestCounters < maxPermits)
- {
- resourcesToAdd = maxPermits - availableRequestCounters;
- }
- else
+ if (availableRequestCounters >= _options.PermitLimit)
{
// All counters available, nothing to do
return;
}
- _requestCount += resourcesToAdd;
- Debug.Assert(_requestCount == _options.PermitLimit);
+ _requestCount = _options.PermitLimit;
// Process queued requests
while (_queue.Count > 0)
diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiterOptions.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiterOptions.cs
index 92cac84012c..8f7dbaa344b 100644
--- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiterOptions.cs
+++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/FixedWindowRateLimiterOptions.cs
@@ -10,7 +10,7 @@ namespace System.Threading.RateLimiting
{
/// <summary>
/// Specifies the time window that takes in the requests.
- /// Must be set to a value >= <see cref="TimeSpan.Zero" /> by the time these options are passed to the constructor of <see cref="FixedWindowRateLimiter"/>.
+ /// Must be set to a value greater than <see cref="TimeSpan.Zero" /> by the time these options are passed to the constructor of <see cref="FixedWindowRateLimiter"/>.
/// </summary>
public TimeSpan Window { get; set; } = TimeSpan.Zero;
diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs
index 1ccf40775e2..5dfc3691448 100644
--- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs
+++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiter.cs
@@ -26,6 +26,7 @@ namespace System.Threading.RateLimiting
private readonly Timer? _renewTimer;
private readonly SlidingWindowRateLimiterOptions _options;
+ private readonly TimeSpan _replenishmentPeriod;
private readonly Deque<RequestRegistration> _queue = new Deque<RequestRegistration>();
// Use the queue as the lock field so we don't need to allocate another object for a lock and have another field in the object
@@ -42,7 +43,7 @@ namespace System.Threading.RateLimiting
public override bool IsAutoReplenishing => _options.AutoReplenishment;
/// <inheritdoc />
- public override TimeSpan ReplenishmentPeriod => new TimeSpan(_options.Window.Ticks / _options.SegmentsPerWindow);
+ public override TimeSpan ReplenishmentPeriod => _replenishmentPeriod;
/// <summary>
/// Initializes the <see cref="SlidingWindowRateLimiter"/>.
@@ -62,9 +63,9 @@ namespace System.Threading.RateLimiting
{
throw new ArgumentException($"{nameof(options.QueueLimit)} must be set to a value greater than or equal to 0.", nameof(options));
}
- if (options.Window < TimeSpan.Zero)
+ if (options.Window <= TimeSpan.Zero)
{
- throw new ArgumentException($"{nameof(options.Window)} must be set to a value greater than or equal to TimeSpan.Zero.", nameof(options));
+ throw new ArgumentException($"{nameof(options.Window)} must be set to a value greater than TimeSpan.Zero.", nameof(options));
}
_options = new SlidingWindowRateLimiterOptions
@@ -78,6 +79,7 @@ namespace System.Threading.RateLimiting
};
_requestCount = options.PermitLimit;
+ _replenishmentPeriod = new TimeSpan(_options.Window.Ticks / _options.SegmentsPerWindow);
// _requestsPerSegment holds the no. of acquired requests in each window segment
_requestsPerSegment = new int[options.SegmentsPerWindow];
@@ -287,7 +289,7 @@ namespace System.Threading.RateLimiting
return;
}
- if ((long)((nowTicks - _lastReplenishmentTick) * TickFrequency) < ReplenishmentPeriod.Ticks)
+ if (((nowTicks - _lastReplenishmentTick) * TickFrequency) < ReplenishmentPeriod.Ticks && !_options.AutoReplenishment)
{
return;
}
diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiterOptions.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiterOptions.cs
index 8e1d397a57f..93f7ba933b4 100644
--- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiterOptions.cs
+++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/SlidingWindowRateLimiterOptions.cs
@@ -10,7 +10,7 @@ namespace System.Threading.RateLimiting
{
/// <summary>
/// Specifies the minimum period between replenishments.
- /// Must be set to a value >= <see cref="TimeSpan.Zero" /> by the time these options are passed to the constructor of <see cref="SlidingWindowRateLimiter"/>.
+ /// Must be set to a value greater than <see cref="TimeSpan.Zero" /> by the time these options are passed to the constructor of <see cref="SlidingWindowRateLimiter"/>.
/// </summary>
public TimeSpan Window { get; set; } = TimeSpan.Zero;
diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiter.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiter.cs
index 7baf91ea590..f1fbcb4433c 100644
--- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiter.cs
+++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiter.cs
@@ -13,7 +13,7 @@ namespace System.Threading.RateLimiting
/// </summary>
public sealed class TokenBucketRateLimiter : ReplenishingRateLimiter
{
- private int _tokenCount;
+ private double _tokenCount;
private int _queueCount;
private long _lastReplenishmentTick;
private long? _idleSince;
@@ -22,6 +22,7 @@ namespace System.Threading.RateLimiting
private long _failedLeasesCount;
private long _successfulLeasesCount;
+ private readonly double _fillRate;
private readonly Timer? _renewTimer;
private readonly TokenBucketRateLimiterOptions _options;
private readonly Deque<RequestRegistration> _queue = new Deque<RequestRegistration>();
@@ -60,9 +61,9 @@ namespace System.Threading.RateLimiting
{
throw new ArgumentException($"{nameof(options.QueueLimit)} must be set to a value greater than or equal to 0.", nameof(options));
}
- if (options.ReplenishmentPeriod < TimeSpan.Zero)
+ if (options.ReplenishmentPeriod <= TimeSpan.Zero)
{
- throw new ArgumentException($"{nameof(options.ReplenishmentPeriod)} must be set to a value greater than or equal to TimeSpan.Zero.", nameof(options));
+ throw new ArgumentException($"{nameof(options.ReplenishmentPeriod)} must be set to a value greater than TimeSpan.Zero.", nameof(options));
}
_options = new TokenBucketRateLimiterOptions
@@ -76,6 +77,7 @@ namespace System.Threading.RateLimiting
};
_tokenCount = options.TokenLimit;
+ _fillRate = (double)options.TokensPerPeriod / options.ReplenishmentPeriod.Ticks;
_idleSince = _lastReplenishmentTick = Stopwatch.GetTimestamp();
@@ -91,7 +93,7 @@ namespace System.Threading.RateLimiting
ThrowIfDisposed();
return new RateLimiterStatistics()
{
- CurrentAvailablePermits = _tokenCount,
+ CurrentAvailablePermits = (long)_tokenCount,
CurrentQueuedCount = _queueCount,
TotalFailedLeases = Interlocked.Read(ref _failedLeasesCount),
TotalSuccessfulLeases = Interlocked.Read(ref _successfulLeasesCount),
@@ -210,7 +212,7 @@ namespace System.Threading.RateLimiting
private RateLimitLease CreateFailedTokenLease(int tokenCount)
{
- int replenishAmount = tokenCount - _tokenCount + _queueCount;
+ int replenishAmount = tokenCount - (int)_tokenCount + _queueCount;
// can't have 0 replenish periods, that would mean it should be a successful lease
// if TokensPerPeriod is larger than the replenishAmount needed then it would be 0
Debug.Assert(_options.TokensPerPeriod > 0);
@@ -278,7 +280,7 @@ namespace System.Threading.RateLimiting
limiter!.ReplenishInternal(nowTicks);
}
- // Used in tests that test behavior with specific time intervals
+ // Used in tests to avoid dealing with real time
private void ReplenishInternal(long nowTicks)
{
// method is re-entrant (from Timer), lock to avoid multiple simultaneous replenishes
@@ -289,37 +291,35 @@ namespace System.Threading.RateLimiting
return;
}
- if ((long)((nowTicks - _lastReplenishmentTick) * TickFrequency) < _options.ReplenishmentPeriod.Ticks)
+ if (_tokenCount == _options.TokenLimit)
{
return;
}
- _lastReplenishmentTick = nowTicks;
-
- int availablePermits = _tokenCount;
- TokenBucketRateLimiterOptions options = _options;
- int maxPermits = options.TokenLimit;
- int resourcesToAdd;
+ double add;
- if (availablePermits < maxPermits)
+ // Trust the timer to be close enough to when we want to replenish, this avoids issues with Timer jitter where it might be .99 seconds instead of 1, and 1.1 seconds the next time etc.
+ if (_options.AutoReplenishment)
{
- resourcesToAdd = Math.Min(options.TokensPerPeriod, maxPermits - availablePermits);
+ add = _options.TokensPerPeriod;
}
else
{
- // All tokens available, nothing to do
- return;
+ add = _fillRate * (nowTicks - _lastReplenishmentTick) * TickFrequency;
}
+ _tokenCount = Math.Min(_options.TokenLimit, _tokenCount + add);
+
+ _lastReplenishmentTick = nowTicks;
+
// Process queued requests
Deque<RequestRegistration> queue = _queue;
- _tokenCount += resourcesToAdd;
Debug.Assert(_tokenCount <= _options.TokenLimit);
while (queue.Count > 0)
{
RequestRegistration nextPendingRequest =
- options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst
+ _options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst
? queue.PeekHead()
: queue.PeekTail();
@@ -327,7 +327,7 @@ namespace System.Threading.RateLimiting
{
// Request can be fulfilled
nextPendingRequest =
- options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst
+ _options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst
? queue.DequeueHead()
: queue.DequeueTail();
diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiterOptions.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiterOptions.cs
index 55b63f65d36..2c065d9432e 100644
--- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiterOptions.cs
+++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/TokenBucketRateLimiterOptions.cs
@@ -10,7 +10,7 @@ namespace System.Threading.RateLimiting
{
/// <summary>
/// Specifies the minimum period between replenishments.
- /// Must be set to a value >= <see cref="TimeSpan.Zero" /> by the time these options are passed to the constructor of <see cref="TokenBucketRateLimiter"/>.
+ /// Must be set to a value greater than <see cref="TimeSpan.Zero" /> by the time these options are passed to the constructor of <see cref="TokenBucketRateLimiter"/>.
/// </summary>
public TimeSpan ReplenishmentPeriod { get; set; } = TimeSpan.Zero;
diff --git a/src/libraries/System.Threading.RateLimiting/tests/FixedWindowRateLimiterTests.cs b/src/libraries/System.Threading.RateLimiting/tests/FixedWindowRateLimiterTests.cs
index 6830a1ce742..1f597748d67 100644
--- a/src/libraries/System.Threading.RateLimiting/tests/FixedWindowRateLimiterTests.cs
+++ b/src/libraries/System.Threading.RateLimiting/tests/FixedWindowRateLimiterTests.cs
@@ -17,7 +17,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire();
@@ -27,7 +27,7 @@ namespace System.Threading.RateLimiting.Test
lease.Dispose();
Assert.False(limiter.AttemptAcquire().IsAcquired);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.True(limiter.AttemptAcquire().IsAcquired);
}
@@ -37,31 +37,49 @@ namespace System.Threading.RateLimiting.Test
{
Assert.Throws<ArgumentException>(
() => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
- {
- PermitLimit = -1,
- QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
- QueueLimit = 1,
- Window = TimeSpan.FromMinutes(2),
- AutoReplenishment = false
- }));
+ {
+ PermitLimit = -1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ Window = TimeSpan.FromMinutes(2),
+ AutoReplenishment = false
+ }));
Assert.Throws<ArgumentException>(
() => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
- {
- PermitLimit = 1,
- QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
- QueueLimit = -1,
- Window = TimeSpan.FromMinutes(2),
- AutoReplenishment = false
- }));
+ {
+ PermitLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = -1,
+ Window = TimeSpan.FromMinutes(2),
+ AutoReplenishment = false
+ }));
Assert.Throws<ArgumentException>(
() => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
- {
- PermitLimit = 1,
- QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
- QueueLimit = 1,
- Window = TimeSpan.MinValue,
- AutoReplenishment = false
- }));
+ {
+ PermitLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ Window = TimeSpan.MinValue,
+ AutoReplenishment = false
+ }));
+ Assert.Throws<ArgumentException>(
+ () => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
+ {
+ PermitLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ Window = TimeSpan.FromMinutes(-2),
+ AutoReplenishment = false,
+ }));
+ Assert.Throws<ArgumentException>(
+ () => new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
+ {
+ PermitLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ Window = TimeSpan.Zero,
+ AutoReplenishment = false,
+ }));
}
[Fact]
@@ -72,7 +90,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
@@ -82,7 +100,7 @@ namespace System.Threading.RateLimiting.Test
var wait = limiter.AcquireAsync();
Assert.False(wait.IsCompleted);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.True((await wait).IsAcquired);
}
@@ -95,7 +113,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 2,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = await limiter.AcquireAsync();
@@ -107,7 +125,7 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait2.IsCompleted);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
lease = await wait1;
Assert.True(lease.IsAcquired);
@@ -115,7 +133,7 @@ namespace System.Threading.RateLimiting.Test
lease.Dispose();
Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
lease = await wait2;
Assert.True(lease.IsAcquired);
@@ -129,7 +147,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 3,
- Window = TimeSpan.FromMinutes(0),
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
@@ -142,7 +160,7 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait2.IsCompleted);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
// second queued item completes first with NewestFirst
lease = await wait2;
@@ -151,7 +169,7 @@ namespace System.Threading.RateLimiting.Test
lease.Dispose();
Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
lease = await wait1;
Assert.True(lease.IsAcquired);
@@ -165,7 +183,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
using var lease = limiter.AttemptAcquire(1);
@@ -174,7 +192,7 @@ namespace System.Threading.RateLimiting.Test
var failedLease = await limiter.AcquireAsync(1);
Assert.False(failedLease.IsAcquired);
Assert.True(failedLease.TryGetMetadata(MetadataName.RetryAfter, out var timeSpan));
- Assert.Equal(TimeSpan.Zero, timeSpan);
+ Assert.Equal(TimeSpan.FromMilliseconds(2), timeSpan);
}
[Fact]
@@ -185,7 +203,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(1);
@@ -197,7 +215,7 @@ namespace System.Threading.RateLimiting.Test
Assert.False(lease1.IsAcquired);
Assert.False(wait2.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait2;
Assert.True(lease.IsAcquired);
@@ -211,7 +229,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 2,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(2);
@@ -229,7 +247,7 @@ namespace System.Threading.RateLimiting.Test
Assert.False(lease2.IsAcquired);
Assert.False(wait3.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait3;
Assert.True(lease.IsAcquired);
@@ -243,7 +261,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(2);
@@ -256,7 +274,7 @@ namespace System.Threading.RateLimiting.Test
var lease1 = await limiter.AcquireAsync(2);
Assert.False(lease1.IsAcquired);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
@@ -270,7 +288,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(1);
@@ -279,14 +297,14 @@ namespace System.Threading.RateLimiting.Test
var failedLease = await limiter.AcquireAsync(1);
Assert.False(failedLease.IsAcquired);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
wait = limiter.AcquireAsync(1);
Assert.False(wait.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
}
@@ -299,7 +317,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = int.MaxValue,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = int.MaxValue,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(int.MaxValue);
@@ -315,7 +333,7 @@ namespace System.Threading.RateLimiting.Test
var lease1 = await wait;
Assert.False(lease1.IsAcquired);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
var lease2 = await wait2;
Assert.True(lease2.IsAcquired);
}
@@ -328,7 +346,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
Assert.Throws<ArgumentOutOfRangeException>(() => limiter.AttemptAcquire(2));
@@ -342,7 +360,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await limiter.AcquireAsync(2));
@@ -356,7 +374,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
Assert.Throws<ArgumentOutOfRangeException>(() => limiter.AttemptAcquire(-1));
@@ -370,7 +388,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
await Assert.ThrowsAsync<ArgumentOutOfRangeException>(async () => await limiter.AcquireAsync(-1));
@@ -384,7 +402,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
@@ -400,7 +418,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
using var lease = limiter.AttemptAcquire(1);
@@ -419,7 +437,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
@@ -435,7 +453,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = await limiter.AcquireAsync(1);
@@ -445,7 +463,7 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait.IsCompleted);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
using var lease2 = await wait;
Assert.True(lease2.IsAcquired);
}
@@ -458,7 +476,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 2,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
using var lease = await limiter.AcquireAsync(2);
@@ -470,7 +488,7 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait2.IsCompleted);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
var lease1 = await wait1;
var lease2 = await wait2;
@@ -486,7 +504,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(1);
@@ -500,7 +518,7 @@ namespace System.Threading.RateLimiting.Test
Assert.Equal(cts.Token, ex.CancellationToken);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits);
}
@@ -513,7 +531,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(1);
@@ -526,7 +544,7 @@ namespace System.Threading.RateLimiting.Test
Assert.Equal(cts.Token, ex.CancellationToken);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits);
}
@@ -539,7 +557,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(1);
@@ -555,7 +573,7 @@ namespace System.Threading.RateLimiting.Test
wait = limiter.AcquireAsync(1);
Assert.False(wait.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
}
@@ -568,7 +586,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
using var lease = limiter.AttemptAcquire(1);
@@ -583,7 +601,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
using var lease = limiter.AttemptAcquire(1);
@@ -598,7 +616,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(1);
@@ -631,7 +649,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(1);
@@ -770,7 +788,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 2,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
@@ -785,7 +803,7 @@ namespace System.Threading.RateLimiting.Test
Assert.True(lease.IsAcquired);
Assert.False(wait.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
@@ -799,7 +817,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
@@ -811,13 +829,13 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait.IsCompleted);
Assert.False(wait2.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
Assert.False(wait2.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait2;
Assert.True(lease.IsAcquired);
@@ -831,7 +849,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 3,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
@@ -845,7 +863,7 @@ namespace System.Threading.RateLimiting.Test
Assert.True(lease.IsAcquired);
Assert.False(wait.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
@@ -859,7 +877,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
@@ -872,7 +890,7 @@ namespace System.Threading.RateLimiting.Test
lease = limiter.AttemptAcquire(1);
Assert.False(lease.IsAcquired);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
@@ -918,11 +936,11 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 2,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
limiter.AttemptAcquire(1);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
Assert.NotNull(limiter.IdleDuration);
}
@@ -962,7 +980,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 2,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(2);
@@ -988,7 +1006,7 @@ namespace System.Threading.RateLimiting.Test
lease = await wait2;
Assert.False(lease.IsAcquired);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait3;
Assert.True(lease.IsAcquired);
}
@@ -1001,7 +1019,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(1);
@@ -1026,7 +1044,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
@@ -1049,7 +1067,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 100,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 50,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
@@ -1093,7 +1111,7 @@ namespace System.Threading.RateLimiting.Test
Assert.Equal(2, stats.TotalFailedLeases);
Assert.Equal(1, stats.TotalSuccessfulLeases);
- limiter.TryReplenish();
+ Replenish(limiter, 1);
await lease2Task;
// success from wait + available + queue
@@ -1112,7 +1130,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 100,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 50,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(0);
@@ -1145,11 +1163,45 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 100,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 50,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
limiter.Dispose();
Assert.Throws<ObjectDisposedException>(limiter.GetStatistics);
}
+
+ [Fact]
+ public void AutoReplenishIgnoresTimerJitter()
+ {
+ var replenishmentPeriod = TimeSpan.FromMinutes(10);
+ using var limiter = new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions
+ {
+ PermitLimit = 10,
+ QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
+ QueueLimit = 1,
+ Window = replenishmentPeriod,
+ AutoReplenishment = true,
+ });
+
+ var lease = limiter.AttemptAcquire(permitCount: 3);
+ Assert.True(lease.IsAcquired);
+
+ Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits);
+
+ // Replenish 1 millisecond less than ReplenishmentPeriod while AutoReplenishment is enabled
+ Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds - 1);
+
+ Assert.Equal(10, limiter.GetStatistics().CurrentAvailablePermits);
+ }
+
+ private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency;
+
+ static internal void Replenish(FixedWindowRateLimiter limiter, long addMilliseconds)
+ {
+ var replenishInternalMethod = typeof(FixedWindowRateLimiter).GetMethod("ReplenishInternal", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!;
+ var internalTick = typeof(FixedWindowRateLimiter).GetField("_lastReplenishmentTick", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!;
+ var currentTick = (long)internalTick.GetValue(limiter);
+ replenishInternalMethod.Invoke(limiter, new object[] { currentTick + addMilliseconds * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) });
+ }
}
}
diff --git a/src/libraries/System.Threading.RateLimiting/tests/SlidingWindowRateLimiterTests.cs b/src/libraries/System.Threading.RateLimiting/tests/SlidingWindowRateLimiterTests.cs
index 7241a39e0f1..66e6cb2d5f2 100644
--- a/src/libraries/System.Threading.RateLimiting/tests/SlidingWindowRateLimiterTests.cs
+++ b/src/libraries/System.Threading.RateLimiting/tests/SlidingWindowRateLimiterTests.cs
@@ -17,7 +17,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -28,8 +28,8 @@ namespace System.Threading.RateLimiting.Test
lease.Dispose();
Assert.False(limiter.AttemptAcquire().IsAcquired);
- Assert.True(limiter.TryReplenish());
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
Assert.True(limiter.AttemptAcquire().IsAcquired);
}
@@ -39,44 +39,64 @@ namespace System.Threading.RateLimiting.Test
{
Assert.Throws<ArgumentException>(
() => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions
- {
- PermitLimit = -1,
- QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
- QueueLimit = 1,
- Window = TimeSpan.FromMinutes(2),
- SegmentsPerWindow = 1,
- AutoReplenishment = false
- }));
+ {
+ PermitLimit = -1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ Window = TimeSpan.FromMinutes(2),
+ SegmentsPerWindow = 1,
+ AutoReplenishment = false
+ }));
Assert.Throws<ArgumentException>(
() => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions
- {
- PermitLimit = 1,
- QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
- QueueLimit = -1,
- Window = TimeSpan.FromMinutes(2),
- SegmentsPerWindow = 1,
- AutoReplenishment = false
- }));
+ {
+ PermitLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = -1,
+ Window = TimeSpan.FromMinutes(2),
+ SegmentsPerWindow = 1,
+ AutoReplenishment = false
+ }));
Assert.Throws<ArgumentException>(
() => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions
- {
- PermitLimit = 1,
- QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
- QueueLimit = 1,
- Window = TimeSpan.FromMinutes(2),
- SegmentsPerWindow = -1,
- AutoReplenishment = false
- }));
+ {
+ PermitLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ Window = TimeSpan.FromMinutes(2),
+ SegmentsPerWindow = -1,
+ AutoReplenishment = false
+ }));
Assert.Throws<ArgumentException>(
() => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions
- {
- PermitLimit = 1,
- QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
- QueueLimit = 1,
- Window = TimeSpan.MinValue,
- SegmentsPerWindow = 1,
- AutoReplenishment = false
- }));
+ {
+ PermitLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ Window = TimeSpan.MinValue,
+ SegmentsPerWindow = 1,
+ AutoReplenishment = false
+ }));
+ Assert.Throws<ArgumentException>(
+ () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions
+ {
+ PermitLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ Window = TimeSpan.FromMinutes(-2),
+ SegmentsPerWindow = 1,
+ AutoReplenishment = false
+ }));
+ Assert.Throws<ArgumentException>(
+ () => new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions
+ {
+ PermitLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ Window = TimeSpan.Zero,
+ SegmentsPerWindow = 1,
+ AutoReplenishment = false
+ }));
}
[Fact]
@@ -87,7 +107,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 4,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(2),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -98,14 +118,14 @@ namespace System.Threading.RateLimiting.Test
var wait = limiter.AcquireAsync(2);
Assert.False(wait.IsCompleted);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.False(wait.IsCompleted);
var wait2 = limiter.AcquireAsync(2);
Assert.False(wait2.IsCompleted);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.True((await wait2).IsAcquired);
}
@@ -121,7 +141,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 4,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 4,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(3),
SegmentsPerWindow = 3,
AutoReplenishment = false
});
@@ -132,19 +152,19 @@ namespace System.Threading.RateLimiting.Test
var wait = limiter.AcquireAsync(3);
Assert.False(wait.IsCompleted);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.False(wait.IsCompleted);
var wait2 = limiter.AcquireAsync(2);
Assert.True(wait2.IsCompleted);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
var wait3 = limiter.AcquireAsync(2);
Assert.False(wait3.IsCompleted);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.True((await wait3).IsAcquired);
Assert.False((await wait).IsAcquired);
@@ -159,7 +179,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
- Window = TimeSpan.FromMinutes(0),
+ Window = TimeSpan.FromMilliseconds(2),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -172,10 +192,10 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait2.IsCompleted);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.False(wait1.IsCompleted);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
lease = await wait1;
Assert.True(lease.IsAcquired);
@@ -183,8 +203,8 @@ namespace System.Threading.RateLimiting.Test
lease.Dispose();
Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits);
- Assert.True(limiter.TryReplenish());
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
lease = await wait2;
Assert.True(lease.IsAcquired);
@@ -198,7 +218,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 3,
- Window = TimeSpan.FromMinutes(0),
+ Window = TimeSpan.FromMilliseconds(2),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -212,10 +232,10 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait2.IsCompleted);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.False(wait2.IsCompleted);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
// second queued item completes first with NewestFirst
lease = await wait2;
Assert.True(lease.IsAcquired);
@@ -223,8 +243,8 @@ namespace System.Threading.RateLimiting.Test
lease.Dispose();
Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits);
- Assert.True(limiter.TryReplenish());
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
lease = await wait1;
Assert.True(lease.IsAcquired);
@@ -238,7 +258,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -257,7 +277,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -270,8 +290,8 @@ namespace System.Threading.RateLimiting.Test
Assert.False(lease1.IsAcquired);
Assert.False(wait2.IsCompleted);
- limiter.TryReplenish();
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
lease = await wait2;
Assert.True(lease.IsAcquired);
@@ -285,7 +305,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 2,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -304,8 +324,8 @@ namespace System.Threading.RateLimiting.Test
Assert.False(lease2.IsAcquired);
Assert.False(wait3.IsCompleted);
- limiter.TryReplenish();
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
lease = await wait3;
Assert.True(lease.IsAcquired);
@@ -319,7 +339,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -333,8 +353,8 @@ namespace System.Threading.RateLimiting.Test
var lease1 = await limiter.AcquireAsync(2);
Assert.False(lease1.IsAcquired);
- limiter.TryReplenish();
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
@@ -348,7 +368,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 3,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 2,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(3),
SegmentsPerWindow = 3,
AutoReplenishment = false
});
@@ -358,20 +378,20 @@ namespace System.Threading.RateLimiting.Test
var failedLease = await limiter.AcquireAsync(2);
Assert.False(failedLease.IsAcquired);
- limiter.TryReplenish();
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
Assert.False(wait.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
wait = limiter.AcquireAsync(2);
Assert.False(wait.IsCompleted);
- limiter.TryReplenish();
- limiter.TryReplenish();
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
@@ -385,7 +405,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = int.MaxValue,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = int.MaxValue,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -402,8 +422,8 @@ namespace System.Threading.RateLimiting.Test
var lease1 = await wait;
Assert.False(lease1.IsAcquired);
- limiter.TryReplenish();
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
var lease2 = await wait2;
Assert.True(lease2.IsAcquired);
}
@@ -416,7 +436,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 1,
AutoReplenishment = false
});
@@ -431,7 +451,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 1,
AutoReplenishment = false
});
@@ -446,7 +466,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 1,
AutoReplenishment = false
});
@@ -461,7 +481,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 1,
AutoReplenishment = false
});
@@ -476,7 +496,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 1,
AutoReplenishment = false
});
@@ -493,7 +513,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 1,
AutoReplenishment = false
});
@@ -513,7 +533,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 1,
AutoReplenishment = false
});
@@ -530,7 +550,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -541,8 +561,8 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait.IsCompleted);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
using var lease2 = await wait;
Assert.True(lease2.IsAcquired);
}
@@ -555,7 +575,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 4,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -568,8 +588,8 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait2.IsCompleted);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
var lease1 = await wait1;
var lease2 = await wait2;
@@ -585,7 +605,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(2),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -600,7 +620,7 @@ namespace System.Threading.RateLimiting.Test
Assert.Equal(cts.Token, ex.CancellationToken);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits);
}
@@ -613,7 +633,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(2),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -627,7 +647,7 @@ namespace System.Threading.RateLimiting.Test
Assert.Equal(cts.Token, ex.CancellationToken);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits);
}
@@ -640,7 +660,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(2),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -657,8 +677,8 @@ namespace System.Threading.RateLimiting.Test
wait = limiter.AcquireAsync(1);
Assert.False(wait.IsCompleted);
- limiter.TryReplenish();
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
@@ -673,7 +693,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -689,7 +709,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 1,
AutoReplenishment = false
});
@@ -705,7 +725,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 1,
AutoReplenishment = false
});
@@ -739,7 +759,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -809,7 +829,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 2,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(3),
SegmentsPerWindow = 3,
AutoReplenishment = false
});
@@ -825,12 +845,12 @@ namespace System.Threading.RateLimiting.Test
Assert.True(lease.IsAcquired);
Assert.False(wait.IsCompleted);
- limiter.TryReplenish();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
Assert.False(wait.IsCompleted);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
}
@@ -843,7 +863,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 3,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 5,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(2),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -856,18 +876,18 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait.IsCompleted);
Assert.False(wait2.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
Assert.False(wait.IsCompleted);
Assert.False(wait2.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
- limiter.TryReplenish();
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
lease = await wait2;
Assert.True(lease.IsAcquired);
@@ -881,7 +901,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 3,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -896,8 +916,8 @@ namespace System.Threading.RateLimiting.Test
Assert.True(lease.IsAcquired);
Assert.False(wait.IsCompleted);
- limiter.TryReplenish();
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
@@ -911,7 +931,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -925,8 +945,8 @@ namespace System.Threading.RateLimiting.Test
lease = limiter.AttemptAcquire(1);
Assert.False(lease.IsAcquired);
- limiter.TryReplenish();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
@@ -974,13 +994,13 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 2,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
limiter.AttemptAcquire(1);
- limiter.TryReplenish();
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
Assert.NotNull(limiter.IdleDuration);
}
@@ -1022,7 +1042,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 2,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(2),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -1041,7 +1061,7 @@ namespace System.Threading.RateLimiting.Test
Assert.Equal(cts.Token, ex.CancellationToken);
lease.Dispose();
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
var wait3 = limiter.AcquireAsync(2);
Assert.False(wait3.IsCompleted);
@@ -1050,7 +1070,7 @@ namespace System.Threading.RateLimiting.Test
lease = await wait2;
Assert.False(lease.IsAcquired);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait3;
Assert.True(lease.IsAcquired);
}
@@ -1063,7 +1083,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(1),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -1089,7 +1109,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(2),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -1113,7 +1133,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 100,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 50,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(2),
SegmentsPerWindow = 2,
AutoReplenishment = false
});
@@ -1138,7 +1158,7 @@ namespace System.Threading.RateLimiting.Test
Assert.Equal(0, stats.TotalFailedLeases);
Assert.Equal(1, stats.TotalSuccessfulLeases);
- limiter.TryReplenish();
+ Replenish(limiter, 1);
var lease3 = await limiter.AcquireAsync(1);
Assert.False(lease3.IsAcquired);
@@ -1156,7 +1176,7 @@ namespace System.Threading.RateLimiting.Test
Assert.Equal(2, stats.TotalFailedLeases);
Assert.Equal(1, stats.TotalSuccessfulLeases);
- limiter.TryReplenish();
+ Replenish(limiter, 1);
await lease2Task;
stats = limiter.GetStatistics();
@@ -1174,7 +1194,7 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 100,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 50,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(3),
SegmentsPerWindow = 3,
AutoReplenishment = false
});
@@ -1208,12 +1228,57 @@ namespace System.Threading.RateLimiting.Test
PermitLimit = 100,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 50,
- Window = TimeSpan.Zero,
+ Window = TimeSpan.FromMilliseconds(3),
SegmentsPerWindow = 3,
AutoReplenishment = false
});
limiter.Dispose();
Assert.Throws<ObjectDisposedException>(limiter.GetStatistics);
}
+
+ [Fact]
+ public void AutoReplenishIgnoresTimerJitter()
+ {
+ var replenishmentPeriod = TimeSpan.FromMinutes(10);
+ using var limiter = new SlidingWindowRateLimiter(new SlidingWindowRateLimiterOptions
+ {
+ PermitLimit = 10,
+ QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
+ QueueLimit = 1,
+ Window = replenishmentPeriod,
+ SegmentsPerWindow = 2,
+ AutoReplenishment = true,
+ });
+
+ var lease = limiter.AttemptAcquire(permitCount: 3);
+ Assert.True(lease.IsAcquired);
+
+ Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits);
+
+ // Replenish 1 millisecond less than ReplenishmentPeriod while AutoReplenishment is enabled
+ Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds / 2 - 1);
+
+ Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits);
+
+ lease = limiter.AttemptAcquire(permitCount: 3);
+ Assert.True(lease.IsAcquired);
+
+ Assert.Equal(4, limiter.GetStatistics().CurrentAvailablePermits);
+
+ // Replenish 1 millisecond longer than ReplenishmentPeriod while AutoReplenishment is enabled
+ Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds / 2 + 1);
+
+ Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits);
+ }
+
+ private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency;
+
+ static internal void Replenish(SlidingWindowRateLimiter limiter, long addMilliseconds)
+ {
+ var replenishInternalMethod = typeof(SlidingWindowRateLimiter).GetMethod("ReplenishInternal", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!;
+ var internalTick = typeof(SlidingWindowRateLimiter).GetField("_lastReplenishmentTick", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!;
+ var currentTick = (long)internalTick.GetValue(limiter);
+ replenishInternalMethod.Invoke(limiter, new object[] { currentTick + addMilliseconds * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) });
+ }
}
}
diff --git a/src/libraries/System.Threading.RateLimiting/tests/TokenBucketRateLimiterTests.cs b/src/libraries/System.Threading.RateLimiting/tests/TokenBucketRateLimiterTests.cs
index 272c294a09b..79c368e2b6d 100644
--- a/src/libraries/System.Threading.RateLimiting/tests/TokenBucketRateLimiterTests.cs
+++ b/src/libraries/System.Threading.RateLimiting/tests/TokenBucketRateLimiterTests.cs
@@ -17,7 +17,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -26,9 +26,10 @@ namespace System.Threading.RateLimiting.Test
Assert.True(lease.IsAcquired);
Assert.False(limiter.AttemptAcquire().IsAcquired);
+ // Dispose doesn't change token count
lease.Dispose();
Assert.False(limiter.AttemptAcquire().IsAcquired);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.True(limiter.AttemptAcquire().IsAcquired);
}
@@ -38,44 +39,64 @@ namespace System.Threading.RateLimiting.Test
{
Assert.Throws<ArgumentException>(
() => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
- {
- TokenLimit = -1,
- QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
- QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.FromMinutes(2),
- TokensPerPeriod = 1,
- AutoReplenishment = false
- }));
+ {
+ TokenLimit = -1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ ReplenishmentPeriod = TimeSpan.FromMinutes(2),
+ TokensPerPeriod = 1,
+ AutoReplenishment = false
+ }));
Assert.Throws<ArgumentException>(
() => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
- {
- TokenLimit = 1,
- QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
- QueueLimit = -1,
- ReplenishmentPeriod = TimeSpan.FromMinutes(2),
- TokensPerPeriod = 1,
- AutoReplenishment = false
- }));
+ {
+ TokenLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = -1,
+ ReplenishmentPeriod = TimeSpan.FromMinutes(2),
+ TokensPerPeriod = 1,
+ AutoReplenishment = false
+ }));
Assert.Throws<ArgumentException>(
() => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
- {
- TokenLimit = 1,
- QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
- QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.FromMinutes(2),
- TokensPerPeriod = -1,
- AutoReplenishment = false
- }));
+ {
+ TokenLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ ReplenishmentPeriod = TimeSpan.FromMinutes(2),
+ TokensPerPeriod = -1,
+ AutoReplenishment = false
+ }));
Assert.Throws<ArgumentException>(
() => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
- {
- TokenLimit = 1,
- QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
- QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.MinValue,
- TokensPerPeriod = 1,
- AutoReplenishment = false
- }));
+ {
+ TokenLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ ReplenishmentPeriod = TimeSpan.MinValue,
+ TokensPerPeriod = 1,
+ AutoReplenishment = false
+ }));
+ Assert.Throws<ArgumentException>(
+ () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
+ {
+ TokenLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(-1),
+ TokensPerPeriod = 1,
+ AutoReplenishment = false
+ }));
+ Assert.Throws<ArgumentException>(
+ () => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
+ {
+ TokenLimit = 1,
+ QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
+ QueueLimit = 1,
+ ReplenishmentPeriod = TimeSpan.Zero,
+ TokensPerPeriod = 1,
+ AutoReplenishment = false
+ }));
}
[Fact]
@@ -86,7 +107,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -97,7 +118,7 @@ namespace System.Threading.RateLimiting.Test
var wait = limiter.AcquireAsync();
Assert.False(wait.IsCompleted);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.True((await wait).IsAcquired);
}
@@ -110,7 +131,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 2,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -123,7 +144,7 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait2.IsCompleted);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
lease = await wait1;
Assert.True(lease.IsAcquired);
@@ -131,7 +152,7 @@ namespace System.Threading.RateLimiting.Test
lease.Dispose();
Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
lease = await wait2;
Assert.True(lease.IsAcquired);
@@ -145,7 +166,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 3,
- ReplenishmentPeriod = TimeSpan.FromMinutes(0),
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -159,7 +180,7 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait2.IsCompleted);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
// second queued item completes first with NewestFirst
lease = await wait2;
@@ -168,8 +189,9 @@ namespace System.Threading.RateLimiting.Test
lease.Dispose();
Assert.Equal(0, limiter.GetStatistics().CurrentAvailablePermits);
- Assert.True(limiter.TryReplenish());
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
+ Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits);
+ Replenish(limiter, 1L);
lease = await wait1;
Assert.True(lease.IsAcquired);
@@ -183,7 +205,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -193,7 +215,7 @@ namespace System.Threading.RateLimiting.Test
var failedLease = await limiter.AcquireAsync(1);
Assert.False(failedLease.IsAcquired);
Assert.True(failedLease.TryGetMetadata(MetadataName.RetryAfter, out var timeSpan));
- Assert.Equal(TimeSpan.Zero, timeSpan);
+ Assert.Equal(TimeSpan.FromMilliseconds(2), timeSpan);
}
[Fact]
@@ -204,7 +226,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -217,7 +239,7 @@ namespace System.Threading.RateLimiting.Test
Assert.False(lease1.IsAcquired);
Assert.False(wait2.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait2;
Assert.True(lease.IsAcquired);
@@ -231,7 +253,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 2,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -250,8 +272,8 @@ namespace System.Threading.RateLimiting.Test
Assert.False(lease2.IsAcquired);
Assert.False(wait3.IsCompleted);
- limiter.TryReplenish();
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
+ Replenish(limiter, 1L);
lease = await wait3;
Assert.True(lease.IsAcquired);
@@ -265,7 +287,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -279,7 +301,7 @@ namespace System.Threading.RateLimiting.Test
var lease1 = await limiter.AcquireAsync(2);
Assert.False(lease1.IsAcquired);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
@@ -293,7 +315,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -303,14 +325,14 @@ namespace System.Threading.RateLimiting.Test
var failedLease = await limiter.AcquireAsync(1);
Assert.False(failedLease.IsAcquired);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
wait = limiter.AcquireAsync(1);
Assert.False(wait.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
}
@@ -323,7 +345,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = int.MaxValue,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = int.MaxValue,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = int.MaxValue,
AutoReplenishment = false
});
@@ -340,7 +362,7 @@ namespace System.Threading.RateLimiting.Test
var lease1 = await wait;
Assert.False(lease1.IsAcquired);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
var lease2 = await wait2;
Assert.True(lease2.IsAcquired);
}
@@ -353,7 +375,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -368,7 +390,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -383,7 +405,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -398,7 +420,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -413,7 +435,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -430,7 +452,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -450,7 +472,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -467,7 +489,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -478,7 +500,7 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait.IsCompleted);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
using var lease2 = await wait;
Assert.True(lease2.IsAcquired);
}
@@ -491,7 +513,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 2,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 2,
AutoReplenishment = false
});
@@ -504,7 +526,7 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait2.IsCompleted);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
var lease1 = await wait1;
var lease2 = await wait2;
@@ -520,7 +542,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -535,7 +557,7 @@ namespace System.Threading.RateLimiting.Test
Assert.Equal(cts.Token, ex.CancellationToken);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits);
}
@@ -548,7 +570,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 2,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 2,
AutoReplenishment = false
});
@@ -575,7 +597,7 @@ namespace System.Threading.RateLimiting.Test
lease = await wait2;
Assert.False(lease.IsAcquired);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait3;
Assert.True(lease.IsAcquired);
}
@@ -588,7 +610,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -614,7 +636,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -628,7 +650,7 @@ namespace System.Threading.RateLimiting.Test
Assert.Equal(cts.Token, ex.CancellationToken);
lease.Dispose();
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.Equal(1, limiter.GetStatistics().CurrentAvailablePermits);
}
@@ -641,7 +663,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -658,7 +680,7 @@ namespace System.Threading.RateLimiting.Test
wait = limiter.AcquireAsync(1);
Assert.False(wait.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
}
@@ -671,7 +693,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -687,7 +709,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -703,7 +725,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -737,7 +759,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
@@ -888,14 +910,14 @@ namespace System.Threading.RateLimiting.Test
}
[Fact]
- public void TryReplenishHonorsTokensPerPeriod()
+ public void ReplenishHonorsTokensPerPeriod()
{
var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
{
TokenLimit = 7,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 3,
AutoReplenishment = false
});
@@ -903,27 +925,28 @@ namespace System.Threading.RateLimiting.Test
Assert.False(limiter.AttemptAcquire(3).IsAcquired);
Assert.Equal(2, limiter.GetStatistics().CurrentAvailablePermits);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.Equal(5, limiter.GetStatistics().CurrentAvailablePermits);
- Assert.True(limiter.TryReplenish());
+ Replenish(limiter, 1L);
Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits);
}
[Fact]
- public void TryReplenishWithAllTokensAvailable_Noops()
+ public async void TryReplenishWithAllTokensAvailable_Noops()
{
var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
{
TokenLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(30),
TokensPerPeriod = 1,
AutoReplenishment = false
});
Assert.Equal(2, limiter.GetStatistics().CurrentAvailablePermits);
- Assert.True(limiter.TryReplenish());
+ await Task.Delay(100);
+ limiter.TryReplenish();
Assert.Equal(2, limiter.GetStatistics().CurrentAvailablePermits);
}
@@ -971,7 +994,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 2,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 2,
AutoReplenishment = false
});
@@ -987,7 +1010,7 @@ namespace System.Threading.RateLimiting.Test
Assert.True(lease.IsAcquired);
Assert.False(wait.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
@@ -1001,7 +1024,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 2,
AutoReplenishment = false
});
@@ -1014,13 +1037,13 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait.IsCompleted);
Assert.False(wait2.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
Assert.False(wait2.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait2;
Assert.True(lease.IsAcquired);
@@ -1034,7 +1057,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.NewestFirst,
QueueLimit = 3,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 2,
AutoReplenishment = false
});
@@ -1049,7 +1072,7 @@ namespace System.Threading.RateLimiting.Test
Assert.True(lease.IsAcquired);
Assert.False(wait.IsCompleted);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
@@ -1063,7 +1086,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 2,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 3,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 2,
AutoReplenishment = false
});
@@ -1077,14 +1100,12 @@ namespace System.Threading.RateLimiting.Test
lease = limiter.AttemptAcquire(1);
Assert.False(lease.IsAcquired);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
lease = await wait;
Assert.True(lease.IsAcquired);
}
- private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency;
-
[Fact]
public async Task ReplenishWorksWithTicksOverInt32Max()
{
@@ -1098,16 +1119,16 @@ namespace System.Threading.RateLimiting.Test
AutoReplenishment = false
});
+ // Ensure next tick is over uint.MaxValue
+ Replenish(limiter, uint.MaxValue);
+
var lease = limiter.AttemptAcquire(10);
Assert.True(lease.IsAcquired);
var wait = limiter.AcquireAsync(1);
Assert.False(wait.IsCompleted);
- var replenishInternalMethod = typeof(TokenBucketRateLimiter).GetMethod("ReplenishInternal", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!;
- // Ensure next tick is over uint.MaxValue
- var tick = Stopwatch.GetTimestamp() + uint.MaxValue;
- replenishInternalMethod.Invoke(limiter, new object[] { tick });
+ Replenish(limiter, 2L);
lease = await wait;
Assert.True(lease.IsAcquired);
@@ -1116,11 +1137,11 @@ namespace System.Threading.RateLimiting.Test
Assert.False(wait.IsCompleted);
// Tick 1 millisecond too soon and verify that the queued item wasn't completed
- replenishInternalMethod.Invoke(limiter, new object[] { tick + 1L * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) });
+ Replenish(limiter, 1L);
Assert.False(wait.IsCompleted);
// ticks would wrap if using uint
- replenishInternalMethod.Invoke(limiter, new object[] { tick + 2L * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) });
+ Replenish(limiter, 2L);
lease = await wait;
Assert.True(lease.IsAcquired);
}
@@ -1167,12 +1188,12 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 2,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 1,
AutoReplenishment = false
});
limiter.AttemptAcquire(1);
- limiter.TryReplenish();
+ Replenish(limiter, 1L);
Assert.NotNull(limiter.IdleDuration);
}
@@ -1214,7 +1235,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 2,
AutoReplenishment = false
});
@@ -1238,7 +1259,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 100,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 50,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 30,
AutoReplenishment = false
});
@@ -1279,7 +1300,7 @@ namespace System.Threading.RateLimiting.Test
Assert.Equal(2, stats.TotalFailedLeases);
Assert.Equal(1, stats.TotalSuccessfulLeases);
- limiter.TryReplenish();
+ Replenish(limiter, 1);
await lease2Task;
stats = limiter.GetStatistics();
@@ -1297,7 +1318,7 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 100,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 50,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 30,
AutoReplenishment = false
});
@@ -1331,12 +1352,82 @@ namespace System.Threading.RateLimiting.Test
TokenLimit = 100,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 50,
- ReplenishmentPeriod = TimeSpan.Zero,
+ ReplenishmentPeriod = TimeSpan.FromMilliseconds(1),
TokensPerPeriod = 30,
AutoReplenishment = false
});
limiter.Dispose();
Assert.Throws<ObjectDisposedException>(limiter.GetStatistics);
}
+
+ [Fact]
+ public void AutoReplenishIgnoresTimerJitter()
+ {
+ var replenishmentPeriod = TimeSpan.FromMinutes(10);
+ using var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
+ {
+ TokenLimit = 10,
+ QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
+ QueueLimit = 1,
+ ReplenishmentPeriod = replenishmentPeriod,
+ AutoReplenishment = true,
+ TokensPerPeriod = 1,
+ });
+
+ var lease = limiter.AttemptAcquire(permitCount: 3);
+ Assert.True(lease.IsAcquired);
+
+ Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits);
+
+ // Replenish 1 millisecond less than ReplenishmentPeriod while AutoReplenishment is enabled
+ Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds - 1);
+
+ Assert.Equal(8, limiter.GetStatistics().CurrentAvailablePermits);
+
+ // Replenish 1 millisecond longer than ReplenishmentPeriod while AutoReplenishment is enabled
+ Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds + 1);
+
+ Assert.Equal(9, limiter.GetStatistics().CurrentAvailablePermits);
+ }
+
+ [Fact]
+ public void ManualReplenishPreservesTimeWithTimerJitter()
+ {
+ var replenishmentPeriod = TimeSpan.FromMinutes(10);
+ using var limiter = new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions
+ {
+ TokenLimit = 10,
+ QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
+ QueueLimit = 1,
+ ReplenishmentPeriod = replenishmentPeriod,
+ AutoReplenishment = false,
+ TokensPerPeriod = 1,
+ });
+
+ var lease = limiter.AttemptAcquire(permitCount: 3);
+ Assert.True(lease.IsAcquired);
+
+ Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits);
+
+ // Replenish 1 millisecond less than ReplenishmentPeriod while AutoReplenishment is enabled
+ Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds - 1);
+
+ Assert.Equal(7, limiter.GetStatistics().CurrentAvailablePermits);
+
+ // Replenish 1 millisecond longer than ReplenishmentPeriod while AutoReplenishment is enabled
+ Replenish(limiter, (long)replenishmentPeriod.TotalMilliseconds + 1);
+
+ Assert.Equal(9, limiter.GetStatistics().CurrentAvailablePermits);
+ }
+
+ private static readonly double TickFrequency = (double)TimeSpan.TicksPerSecond / Stopwatch.Frequency;
+
+ static internal void Replenish(TokenBucketRateLimiter limiter, long addMilliseconds)
+ {
+ var replenishInternalMethod = typeof(TokenBucketRateLimiter).GetMethod("ReplenishInternal", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!;
+ var internalTick = typeof(TokenBucketRateLimiter).GetField("_lastReplenishmentTick", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!;
+ var currentTick = (long)internalTick.GetValue(limiter);
+ replenishInternalMethod.Invoke(limiter, new object[] { currentTick + addMilliseconds * (long)(TimeSpan.TicksPerMillisecond / TickFrequency) });
+ }
}
}