<PackageReference Include="Polly" Version="8.3.0" />

LockFreeTokenBucketRateLimiter

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; this.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); } } }