LockFreeTokenBucketRateLimiter
A lock-free token-bucket rate-limiter for a Polly IRateLimitPolicy.
using Polly.Utilities;
using System;
using System.Threading;
namespace Polly.RateLimit
{
internal sealed class LockFreeTokenBucketRateLimiter : IRateLimiter
{
private readonly long _addTokenTickInterval;
private readonly long _bucketCapacity;
private long _currentTokens;
private long _addNextTokenAtTicks;
private SpinWait _spinner;
public LockFreeTokenBucketRateLimiter(TimeSpan onePer, long bucketCapacity)
{
if (onePer <= TimeSpan.Zero)
throw new ArgumentOutOfRangeException("onePer", onePer, "The LockFreeTokenBucketRateLimiter must specify a positive TimeSpan for how often an execution is permitted.");
if (bucketCapacity <= 0)
throw new ArgumentOutOfRangeException("bucketCapacity", bucketCapacity, "bucketCapacity must be greater than or equal to 1.");
_addTokenTickInterval = onePer.Ticks;
_bucketCapacity = bucketCapacity;
_currentTokens = bucketCapacity;
_addNextTokenAtTicks = SystemClock.DateTimeOffsetUtcNow().Ticks + _addTokenTickInterval;
}
public (bool PermitExecution, TimeSpan RetryAfter) PermitExecution()
{
long num3;
while (true) {
if (Interlocked.Decrement(ref _currentTokens) >= 0)
return (true, TimeSpan.Zero);
long ticks = SystemClock.DateTimeOffsetUtcNow().Ticks;
long num = Interlocked.Read(ref _addNextTokenAtTicks);
long num2 = num - ticks;
if (num2 > 0)
return (false, TimeSpan.FromTicks(num2));
long val = 1 + -num2 / _addTokenTickInterval;
num3 = Math.Min(_bucketCapacity, val);
long val2 = num + num3 * _addTokenTickInterval;
val2 = Math.Max(val2, ticks + _addTokenTickInterval);
if (Interlocked.CompareExchange(ref _addNextTokenAtTicks, val2, num) == num)
break;
_spinner.SpinOnce();
}
Interlocked.Exchange(ref _currentTokens, num3 - 1);
return (true, TimeSpan.Zero);
}
}
}