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
Next Next commit
[RateLimiting] TryReplenish handles multiple replenish periods at a time
  • Loading branch information
BrennanConroy authored and github-actions committed Sep 1, 2022
commit fcc4e014f6cd1037257b3bdffe5170e49a437bc4
Original file line number Diff line number Diff line change
Expand Up @@ -287,29 +287,25 @@ private void ReplenishInternal(long nowTicks)
return;
}

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

_lastReplenishmentTick = nowTicks;
// 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);

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)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ public sealed class SlidingWindowRateLimiter : ReplenishingRateLimiter

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
Expand All @@ -42,7 +43,7 @@ public sealed class SlidingWindowRateLimiter : ReplenishingRateLimiter
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"/>.
Expand Down Expand Up @@ -78,6 +79,7 @@ public SlidingWindowRateLimiter(SlidingWindowRateLimiterOptions options)
};

_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];
Expand Down Expand Up @@ -287,27 +289,39 @@ private void ReplenishInternal(long nowTicks)
return;
}

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

_lastReplenishmentTick = nowTicks;
// 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);

// 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;
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;

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

if (initialRequestCount == _requestCount)
{
// no requests added, queued items don't need updating
return;
}

_requestCount += oldSegmentRequestCount;
Debug.Assert(_requestCount <= _options.PermitLimit);

// Process queued requests
while (_queue.Count > 0)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ private static void Replenish(object? state)
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
Expand All @@ -289,21 +289,23 @@ private void ReplenishInternal(long nowTicks)
return;
}

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

_lastReplenishmentTick = nowTicks;
// 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);

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

if (availablePermits < maxPermits)
{
resourcesToAdd = Math.Min(options.TokensPerPeriod, maxPermits - availablePermits);
resourcesToAdd = (int)Math.Min(_options.TokensPerPeriod * periods, maxPermits - availablePermits);
}
else
{
Expand All @@ -319,15 +321,15 @@ private void ReplenishInternal(long nowTicks)
while (queue.Count > 0)
{
RequestRegistration nextPendingRequest =
options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst
_options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst
? queue.PeekHead()
: queue.PeekTail();

if (_tokenCount >= nextPendingRequest.Count)
{
// Request can be fulfilled
nextPendingRequest =
options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst
_options.QueueProcessingOrder == QueueProcessingOrder.OldestFirst
? queue.DequeueHead()
: queue.DequeueTail();

Expand Down
Loading