Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
ignore tick on auto
  • Loading branch information
BrennanConroy authored and github-actions committed Sep 1, 2022
commit 68a01ca4932372ce60f981fcd3c93e8c146b13df
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,9 @@ public FixedWindowRateLimiter(FixedWindowRateLimiterOptions options)
{
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
Expand Down Expand Up @@ -287,15 +287,14 @@ private void ReplenishInternal(long nowTicks)
return;
}

long periods = (long)((nowTicks - _lastReplenishmentTick) * TickFrequency) / _options.Window.Ticks;
if (periods == 0)
if (((nowTicks - _lastReplenishmentTick) * TickFrequency) < _options.Window.Ticks && !_options.AutoReplenishment)
{
return;
}

// increment last tick by the number of replenish periods that occurred since the last replenish
// this way if replenish isn't being called every ReplenishmentPeriod we correctly track it so we know when replenishes should be occurring
_lastReplenishmentTick += (long)(periods * ReplenishmentPeriod.Ticks / TickFrequency);
_lastReplenishmentTick = nowTicks;

int availableRequestCounters = _requestCount;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public sealed class FixedWindowRateLimiterOptions
{
/// <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 > <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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,9 @@ public SlidingWindowRateLimiter(SlidingWindowRateLimiterOptions options)
{
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
Expand Down Expand Up @@ -289,32 +289,27 @@ private void ReplenishInternal(long nowTicks)
return;
}

long periods = (long)((nowTicks - _lastReplenishmentTick) * TickFrequency) / ReplenishmentPeriod.Ticks;
if (periods == 0)
if (((nowTicks - _lastReplenishmentTick) * TickFrequency) < ReplenishmentPeriod.Ticks && !_options.AutoReplenishment)
{
return;
}

// increment last tick by the number of replenish periods that occurred since the last replenish
// this way if replenish isn't being called every ReplenishmentPeriod we correctly track it so we know when replenishes should be occurring
_lastReplenishmentTick += (long)(periods * ReplenishmentPeriod.Ticks / TickFrequency);
_lastReplenishmentTick = nowTicks;

int initialRequestCount = _requestCount;
do
{
// Increment the current segment index while move the window
// We need to know the no. of requests that were acquired in a segment previously to ensure that we don't acquire more than the permit limit.
_currentSegmentIndex = (_currentSegmentIndex + 1) % _options.SegmentsPerWindow;
int oldSegmentRequestCount = _requestsPerSegment[_currentSegmentIndex];
_requestsPerSegment[_currentSegmentIndex] = 0;
// Increment the current segment index while move the window
// We need to know the no. of requests that were acquired in a segment previously to ensure that we don't acquire more than the permit limit.
_currentSegmentIndex = (_currentSegmentIndex + 1) % _options.SegmentsPerWindow;
int oldSegmentRequestCount = _requestsPerSegment[_currentSegmentIndex];
_requestsPerSegment[_currentSegmentIndex] = 0;

if (oldSegmentRequestCount != 0)
{
_requestCount += oldSegmentRequestCount;
Debug.Assert(_requestCount <= _options.PermitLimit);
}
periods--;
} while (periods > 0);
if (oldSegmentRequestCount != 0)
{
_requestCount += oldSegmentRequestCount;
Debug.Assert(_requestCount <= _options.PermitLimit);
}

if (initialRequestCount == _requestCount)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public sealed class SlidingWindowRateLimiterOptions
{
/// <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 > <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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,9 @@ public TokenBucketRateLimiter(TokenBucketRateLimiterOptions options)
{
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
Expand Down Expand Up @@ -289,23 +289,20 @@ private void ReplenishInternal(long nowTicks)
return;
}

long periods = (long)((nowTicks - _lastReplenishmentTick) * TickFrequency) / _options.ReplenishmentPeriod.Ticks;
if (periods == 0)
if (((nowTicks - _lastReplenishmentTick) * TickFrequency) < _options.ReplenishmentPeriod.Ticks && !_options.AutoReplenishment)
{
return;
}

// increment last tick by the number of replenish periods that occurred since the last replenish
// this way if replenish isn't being called every ReplenishmentPeriod we correctly track it so we know when replenishes should be occurring
_lastReplenishmentTick += (long)(periods * _options.ReplenishmentPeriod.Ticks / TickFrequency);
_lastReplenishmentTick = nowTicks;

int availablePermits = _tokenCount;
int maxPermits = _options.TokenLimit;
int resourcesToAdd;

if (availablePermits < maxPermits)
{
resourcesToAdd = (int)Math.Min(_options.TokensPerPeriod * periods, maxPermits - availablePermits);
resourcesToAdd = Math.Min(maxPermits - availablePermits, _options.TokensPerPeriod);
}
else
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ public sealed class TokenBucketRateLimiterOptions
{
/// <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 > <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;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,10 +62,24 @@ public override void InvalidOptionsThrows()
Window = TimeSpan.MinValue,
AutoReplenishment = false
}));
Assert.Throws<ArgumentOutOfRangeException>(
() => new FixedWindowRateLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1, TimeSpan.FromMinutes(-2), autoReplenishment: false));
Assert.Throws<ArgumentOutOfRangeException>(
() => new FixedWindowRateLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1, TimeSpan.Zero, 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]
Expand Down Expand Up @@ -766,22 +780,6 @@ public async Task AutoReplenish_ReplenishesCounters()
Assert.True(lease.IsAcquired);
}

public void ReplenishAfterMultiplePeriodsIncreaseTokensBasedOnNumberOfPeriods()
{
var limiter = new FixedWindowRateLimiter(new FixedWindowRateLimiterOptions(70, QueueProcessingOrder.OldestFirst, 1,
TimeSpan.FromMilliseconds(1), autoReplenishment: false));
Assert.True(limiter.Acquire(50).IsAcquired);
Assert.False(limiter.Acquire(30).IsAcquired);

Assert.Equal(20, limiter.GetAvailablePermits());
Replenish(limiter, 2L);
Assert.Equal(70, limiter.GetAvailablePermits());

Assert.True(limiter.Acquire(50).IsAcquired);
Replenish(limiter, 5L);
Assert.Equal(70, limiter.GetAvailablePermits());
}

[Fact]
public override async Task CanAcquireResourcesWithAcquireAsyncWithQueuedItemsIfNewestFirst()
{
Expand Down Expand Up @@ -1046,7 +1044,7 @@ public override void GetStatisticsReturnsNewInstances()
PermitLimit = 1,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 1,
Window = TimeSpan.Zero,
Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});

Expand All @@ -1069,7 +1067,7 @@ public override async Task GetStatisticsHasCorrectValues()
PermitLimit = 100,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 50,
Window = TimeSpan.Zero,
Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});

Expand Down Expand Up @@ -1113,7 +1111,7 @@ public override async Task GetStatisticsHasCorrectValues()
Assert.Equal(2, stats.TotalFailedLeases);
Assert.Equal(1, stats.TotalSuccessfulLeases);

limiter.TryReplenish();
Replenish(limiter, 1);
await lease2Task;

// success from wait + available + queue
Expand All @@ -1132,7 +1130,7 @@ public override async Task GetStatisticsWithZeroPermitCount()
PermitLimit = 100,
QueueProcessingOrder = QueueProcessingOrder.OldestFirst,
QueueLimit = 50,
Window = TimeSpan.Zero,
Window = TimeSpan.FromMilliseconds(1),
AutoReplenishment = false
});
var lease = limiter.AttemptAcquire(0);
Expand Down Expand Up @@ -1165,13 +1163,37 @@ public override void GetStatisticsThrowsAfterDispose()
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)
Expand Down
Loading