From e6b22afbeada0daaef4e904b80b4e713193e3885 Mon Sep 17 00:00:00 2001 From: Shuai Zhang Date: Fri, 23 Sep 2022 22:43:06 +0800 Subject: [PATCH] style: fix issues in RateLimiter. --- .../FakeStopwatchProviderAndBlocker.cs | 11 +- .../RateLimiter.Tests/RateLimiterTest.cs | 63 +++++----- RateLimiter/RateLimiter/AssemblyInfo.cs | 1 - RateLimiter/RateLimiter/IAsyncBlocker.cs | 1 - RateLimiter/RateLimiter/IRateLimiter.cs | 1 - RateLimiter/RateLimiter/RateLimiter.cs | 5 +- RateLimiter/RateLimiter/RateLimiter.csproj | 2 +- RateLimiter/RateLimiter/RateLimiterBase.cs | 110 ++++++++++-------- .../RateLimiter/SmoothBurstyRateLimiter.cs | 15 ++- RateLimiter/RateLimiter/SmoothRateLimiter.cs | 59 +++++----- .../RateLimiter/SmoothWarmingUpRateLimiter.cs | 44 +++---- RateLimiter/RateLimiter/TimeSpanExtensions.cs | 1 - RateLimiter/RateLimiter/TryAcquireResult.cs | 1 - 13 files changed, 156 insertions(+), 158 deletions(-) diff --git a/RateLimiter/RateLimiter.Tests/FakeStopwatchProviderAndBlocker.cs b/RateLimiter/RateLimiter.Tests/FakeStopwatchProviderAndBlocker.cs index 7fdf30ce..a503f135 100644 --- a/RateLimiter/RateLimiter.Tests/FakeStopwatchProviderAndBlocker.cs +++ b/RateLimiter/RateLimiter.Tests/FakeStopwatchProviderAndBlocker.cs @@ -15,11 +15,13 @@ internal class FakeStopwatchProviderAndBlocker : IStopwatchProvider, IAsyn { private long instant = 0; + public bool IsHighResolution => throw new NotImplementedException(); + internal IList Events { get; } = new List(); public long GetTimestamp() { - return instant; + return this.instant; } public TimeSpan ParseDuration(long from, long to) @@ -34,13 +36,11 @@ public long GetNextTimestamp(long from, TimeSpan interval) public Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) { - instant += timeout.Ticks; - Events.Add(timeout.Ticks); + this.instant += timeout.Ticks; + this.Events.Add(timeout.Ticks); return Task.FromResult(null); } - public bool IsHighResolution => throw new NotImplementedException(); - public IStopwatch Create() { throw new NotImplementedException(); @@ -52,4 +52,3 @@ public IStopwatch StartNew() } } } - diff --git a/RateLimiter/RateLimiter.Tests/RateLimiterTest.cs b/RateLimiter/RateLimiter.Tests/RateLimiterTest.cs index a49594f1..6efb85cf 100644 --- a/RateLimiter/RateLimiter.Tests/RateLimiterTest.cs +++ b/RateLimiter/RateLimiter.Tests/RateLimiterTest.cs @@ -11,47 +11,26 @@ namespace RateLimiter.Tests { public class RateLimiterTest { - private readonly FakeStopwatchProviderAndBlocker stopwatchProviderAndBlocker = - new FakeStopwatchProviderAndBlocker(); - - internal IRateLimiter Create(double permitsPerSecond) - { - return Create(permitsPerSecond, 1.0); - } - - internal IRateLimiter Create(double permitsPerSecond, double maxBurstSeconds) - { - return new SmoothBurstyRateLimiter(stopwatchProviderAndBlocker, maxBurstSeconds, stopwatchProviderAndBlocker) - { - PermitsPerSecond = permitsPerSecond - }; - } - - internal IRateLimiter Create(double permitsPerSecond, TimeSpan warmupPeriod) - { - return new SmoothWarmingUpRateLimiter(stopwatchProviderAndBlocker, warmupPeriod, 3, stopwatchProviderAndBlocker) - { - PermitsPerSecond = permitsPerSecond - }; - } + private readonly FakeStopwatchProviderAndBlocker stopwatchProviderAndBlocker = new (); [Fact] public void TestSimple() { - var limiter = Create(5, 1); + var limiter = this.Create(5, 1); limiter.Acquire(); // R0.00, since it's the first request limiter.Acquire(); // R0.20 limiter.Acquire(); // R0.20 - Assert.Equal(new[] + Assert.Equal( + new[] { - 0L, TimeSpan.FromSeconds(0.2).Ticks, TimeSpan.FromSeconds(0.2).Ticks - }, stopwatchProviderAndBlocker.Events); + 0L, TimeSpan.FromSeconds(0.2).Ticks, TimeSpan.FromSeconds(0.2).Ticks, + }, this.stopwatchProviderAndBlocker.Events); } [Fact] public void TestImmediateTryAcquire() { - var limiter = Create(1); + var limiter = this.Create(1); Assert.True(limiter.TryAcquire().Succeed, "Unable to acquire initial permit"); Assert.False(limiter.TryAcquire().Succeed, "Capable of acquiring secondary permit"); } @@ -59,17 +38,17 @@ public void TestImmediateTryAcquire() [Fact] public void TestDoubleMinValueCanAcquireExactlyOnce() { - var r = Create(double.Epsilon); + var r = this.Create(double.Epsilon); Assert.True(r.TryAcquire().Succeed, "Unable to acquire initial permit"); Assert.False(r.TryAcquire().Succeed, "Capable of acquiring an additional permit"); - stopwatchProviderAndBlocker.WaitAsync(TimeSpan.MaxValue.Subtract(TimeSpan.FromTicks(1)), CancellationToken.None).GetAwaiter().GetResult(); + this.stopwatchProviderAndBlocker.WaitAsync(TimeSpan.MaxValue.Subtract(TimeSpan.FromTicks(1)), CancellationToken.None).GetAwaiter().GetResult(); Assert.False(r.TryAcquire().Succeed, "Capable of acquiring an additional permit after sleeping"); } [Fact] public void TestSimpleRateUpdate() { - var limiter = Create(5.0, TimeSpan.FromSeconds(5)); + var limiter = this.Create(5.0, TimeSpan.FromSeconds(5)); Assert.Equal(5.0, limiter.PermitsPerSecond); limiter.PermitsPerSecond = 10.0; Assert.Equal(10.0, limiter.PermitsPerSecond); @@ -77,6 +56,26 @@ public void TestSimpleRateUpdate() Assert.Throws(() => limiter.PermitsPerSecond = 0); Assert.Throws(() => limiter.PermitsPerSecond = -10); } + + internal IRateLimiter Create(double permitsPerSecond) + { + return this.Create(permitsPerSecond, 1.0); + } + + internal IRateLimiter Create(double permitsPerSecond, double maxBurstSeconds) + { + return new SmoothBurstyRateLimiter(this.stopwatchProviderAndBlocker, maxBurstSeconds, this.stopwatchProviderAndBlocker) + { + PermitsPerSecond = permitsPerSecond, + }; + } + + internal IRateLimiter Create(double permitsPerSecond, TimeSpan warmupPeriod) + { + return new SmoothWarmingUpRateLimiter(this.stopwatchProviderAndBlocker, warmupPeriod, 3, this.stopwatchProviderAndBlocker) + { + PermitsPerSecond = permitsPerSecond, + }; + } } } - diff --git a/RateLimiter/RateLimiter/AssemblyInfo.cs b/RateLimiter/RateLimiter/AssemblyInfo.cs index 9848419a..c2642180 100644 --- a/RateLimiter/RateLimiter/AssemblyInfo.cs +++ b/RateLimiter/RateLimiter/AssemblyInfo.cs @@ -6,4 +6,3 @@ using System.Runtime.CompilerServices; [assembly: InternalsVisibleTo("RateLimiter.Tests")] - diff --git a/RateLimiter/RateLimiter/IAsyncBlocker.cs b/RateLimiter/RateLimiter/IAsyncBlocker.cs index 0d286ac8..aa1f9e58 100644 --- a/RateLimiter/RateLimiter/IAsyncBlocker.cs +++ b/RateLimiter/RateLimiter/IAsyncBlocker.cs @@ -24,4 +24,3 @@ public Task WaitAsync(TimeSpan timeout, CancellationToken cancellationToken) } } } - diff --git a/RateLimiter/RateLimiter/IRateLimiter.cs b/RateLimiter/RateLimiter/IRateLimiter.cs index bfbd6491..36dbb11a 100644 --- a/RateLimiter/RateLimiter/IRateLimiter.cs +++ b/RateLimiter/RateLimiter/IRateLimiter.cs @@ -70,4 +70,3 @@ public interface IRateLimiter #endif } } - diff --git a/RateLimiter/RateLimiter/RateLimiter.cs b/RateLimiter/RateLimiter/RateLimiter.cs index e89de4f0..6a786c2e 100644 --- a/RateLimiter/RateLimiter/RateLimiter.cs +++ b/RateLimiter/RateLimiter/RateLimiter.cs @@ -17,7 +17,7 @@ public static IRateLimiter CreateBursty( { return new SmoothBurstyRateLimiter(stopwatchProvider, maxBurstSeconds) { - PermitsPerSecond = permitsPerSecond + PermitsPerSecond = permitsPerSecond, }; } @@ -29,7 +29,7 @@ public static IRateLimiter CreateWarmingUp( { return new SmoothWarmingUpRateLimiter(stopwatchProvider, warmupPeriod, coldFactor) { - PermitsPerSecond = permitsPerSecond + PermitsPerSecond = permitsPerSecond, }; } @@ -49,4 +49,3 @@ public static IRateLimiter Create( } } } - diff --git a/RateLimiter/RateLimiter/RateLimiter.csproj b/RateLimiter/RateLimiter/RateLimiter.csproj index 0a835e4e..9ff35d34 100644 --- a/RateLimiter/RateLimiter/RateLimiter.csproj +++ b/RateLimiter/RateLimiter/RateLimiter.csproj @@ -13,7 +13,7 @@ - + diff --git a/RateLimiter/RateLimiter/RateLimiterBase.cs b/RateLimiter/RateLimiter/RateLimiterBase.cs index 10c23917..0365ef0c 100644 --- a/RateLimiter/RateLimiter/RateLimiterBase.cs +++ b/RateLimiter/RateLimiter/RateLimiterBase.cs @@ -15,12 +15,12 @@ namespace RateLimiter { public abstract class RateLimiterBase : IRateLimiter { - protected readonly IStopwatchProvider stopwatchProvider; + internal readonly IStopwatchProvider StopwatchProvider; #if NET20 - protected readonly object lockObject = new object(); + private readonly object lockObject = new object(); #else - protected readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); - internal readonly IAsyncBlocker asyncBlocker; + private readonly SemaphoreSlim semaphoreSlim = new SemaphoreSlim(1, 1); + private readonly IAsyncBlocker asyncBlocker; #endif protected RateLimiterBase(IStopwatchProvider stopwatchProvider) @@ -37,7 +37,7 @@ internal RateLimiterBase( #else { #endif - this.stopwatchProvider = stopwatchProvider ?? throw new ArgumentNullException(nameof(stopwatchProvider)); + this.StopwatchProvider = stopwatchProvider ?? throw new ArgumentNullException(nameof(stopwatchProvider)); } public double PermitsPerSecond @@ -47,41 +47,44 @@ public double PermitsPerSecond #if NET20 Monitor.Enter(lockObject); #else - semaphoreSlim.Wait(); + this.semaphoreSlim.Wait(); #endif try { - return DoGetRate(); + return this.DoGetRate(); } finally { #if NET20 Monitor.Exit(lockObject); #else - semaphoreSlim.Release(); + this.semaphoreSlim.Release(); #endif } } + set { - if (!(value > 0 && !Double.IsNaN(value))) - throw new ArgumentOutOfRangeException(nameof(PermitsPerSecond)); + if (!(value > 0 && !double.IsNaN(value))) + { + throw new ArgumentOutOfRangeException(nameof(this.PermitsPerSecond)); + } #if NET20 Monitor.Enter(lockObject); #else - semaphoreSlim.Wait(); + this.semaphoreSlim.Wait(); #endif try { - DoSetRate(value, stopwatchProvider.GetTimestamp()); + this.DoSetRate(value, this.StopwatchProvider.GetTimestamp()); } finally { #if NET20 Monitor.Exit(lockObject); #else - semaphoreSlim.Release(); + this.semaphoreSlim.Release(); #endif } } @@ -90,20 +93,20 @@ public double PermitsPerSecond [DebuggerStepThrough] public TimeSpan Acquire() { - return Acquire(1); + return this.Acquire(1); } #if !NET20 [DebuggerStepThrough] public Task AcquireAsync() { - return AcquireAsync(CancellationToken.None); + return this.AcquireAsync(CancellationToken.None); } [DebuggerStepThrough] public Task AcquireAsync(CancellationToken cancellationToken) { - return AcquireAsync(1, cancellationToken); + return this.AcquireAsync(1, cancellationToken); } #endif @@ -113,19 +116,19 @@ public Task AcquireAsync(CancellationToken cancellationToken) public TimeSpan Acquire(int permits) #if !NET20 { - return AcquireAsync(permits).GetAwaiter().GetResult(); + return this.AcquireAsync(permits).GetAwaiter().GetResult(); } [DebuggerStepThrough] public Task AcquireAsync(int permits) { - return AcquireAsync(permits, CancellationToken.None); + return this.AcquireAsync(permits, CancellationToken.None); } public async Task AcquireAsync(int permits, CancellationToken cancellationToken) { - var waitTimeout = await ReserveAsync(permits, cancellationToken); - await asyncBlocker.WaitAsync(waitTimeout, cancellationToken); + var waitTimeout = await this.ReserveAsync(permits, cancellationToken); + await this.asyncBlocker.WaitAsync(waitTimeout, cancellationToken); return waitTimeout; } #else @@ -139,60 +142,60 @@ public async Task AcquireAsync(int permits, CancellationToken cancella [DebuggerStepThrough] public TryAcquireResult TryAcquire() { - return TryAcquire(1, TimeSpan.Zero); + return this.TryAcquire(1, TimeSpan.Zero); } #if !NET20 [DebuggerStepThrough] public Task TryAcquireAsync() { - return TryAcquireAsync(CancellationToken.None); + return this.TryAcquireAsync(CancellationToken.None); } [DebuggerStepThrough] public Task TryAcquireAsync(CancellationToken cancellationToken) { - return TryAcquireAsync(1, TimeSpan.Zero, cancellationToken); + return this.TryAcquireAsync(1, TimeSpan.Zero, cancellationToken); } #endif [DebuggerStepThrough] public TryAcquireResult TryAcquire(int permits) { - return TryAcquire(permits, TimeSpan.Zero); + return this.TryAcquire(permits, TimeSpan.Zero); } #if !NET20 [DebuggerStepThrough] public Task TryAcquireAsync(int permits) { - return TryAcquireAsync(permits, CancellationToken.None); + return this.TryAcquireAsync(permits, CancellationToken.None); } [DebuggerStepThrough] public Task TryAcquireAsync(int permits, CancellationToken cancellationToken) { - return TryAcquireAsync(permits, TimeSpan.Zero, cancellationToken); + return this.TryAcquireAsync(permits, TimeSpan.Zero, cancellationToken); } #endif [DebuggerStepThrough] public TryAcquireResult TryAcquire(TimeSpan timeout) { - return TryAcquire(1, timeout); + return this.TryAcquire(1, timeout); } #if !NET20 [DebuggerStepThrough] public Task TryAcquireAsync(TimeSpan timeout) { - return TryAcquireAsync(timeout, CancellationToken.None); + return this.TryAcquireAsync(timeout, CancellationToken.None); } [DebuggerStepThrough] public Task TryAcquireAsync(TimeSpan timeout, CancellationToken cancellationToken) { - return TryAcquireAsync(1, timeout, cancellationToken); + return this.TryAcquireAsync(1, timeout, cancellationToken); } #endif @@ -202,40 +205,43 @@ public Task TryAcquireAsync(TimeSpan timeout, CancellationToke public TryAcquireResult TryAcquire(int permits, TimeSpan timeout) #if !NET20 { - return TryAcquireAsync(permits, timeout, CancellationToken.None).GetAwaiter().GetResult(); + return this.TryAcquireAsync(permits, timeout, CancellationToken.None).GetAwaiter().GetResult(); } [DebuggerStepThrough] public Task TryAcquireAsync(int permits, TimeSpan timeout) { - return TryAcquireAsync(permits, timeout, CancellationToken.None); + return this.TryAcquireAsync(permits, timeout, CancellationToken.None); } public async Task TryAcquireAsync(int permits, TimeSpan timeout, CancellationToken cancellationToken) #endif { - if (!(permits > 0)) throw new ArgumentOutOfRangeException(nameof(permits)); + if (!(permits > 0)) + { + throw new ArgumentOutOfRangeException(nameof(permits)); + } TimeSpan waitTimeout; #if NET20 Monitor.Enter(lockObject); #else - await semaphoreSlim.WaitAsync(cancellationToken); + await this.semaphoreSlim.WaitAsync(cancellationToken); #endif try { - var nowTimestamp = stopwatchProvider.GetTimestamp(); - if (!CanAcquire(nowTimestamp, timeout, out var momentAvailableInterval)) + var nowTimestamp = this.StopwatchProvider.GetTimestamp(); + if (!this.CanAcquire(nowTimestamp, timeout, out var momentAvailableInterval)) { return new TryAcquireResult { Succeed = false, - MomentAvailableInterval = momentAvailableInterval + MomentAvailableInterval = momentAvailableInterval, }; } else { - waitTimeout = ReserveAndGetWaitLength(permits, nowTimestamp); + waitTimeout = this.ReserveAndGetWaitLength(permits, nowTimestamp); } } finally @@ -243,19 +249,19 @@ public async Task TryAcquireAsync(int permits, TimeSpan timeou #if NET20 Monitor.Exit(lockObject); #else - semaphoreSlim.Release(); + this.semaphoreSlim.Release(); #endif } #if NET20 Thread.Sleep(waitTimeout); #else - await asyncBlocker.WaitAsync(waitTimeout, cancellationToken); + await this.asyncBlocker.WaitAsync(waitTimeout, cancellationToken); #endif return new TryAcquireResult { Succeed = true, - MomentAvailableInterval = TimeSpan.Zero + MomentAvailableInterval = TimeSpan.Zero, }; } @@ -265,35 +271,38 @@ public async Task TryAcquireAsync(int permits, TimeSpan timeou public TimeSpan Reserve(int permits) #if !NET20 { - return ReserveAsync(permits).GetAwaiter().GetResult(); + return this.ReserveAsync(permits).GetAwaiter().GetResult(); } [DebuggerStepThrough] public Task ReserveAsync(int permits) { - return ReserveAsync(permits, CancellationToken.None); + return this.ReserveAsync(permits, CancellationToken.None); } public async Task ReserveAsync(int permits, CancellationToken cancellationToken) #endif { - if (!(permits > 0)) throw new ArgumentOutOfRangeException(nameof(permits)); + if (!(permits > 0)) + { + throw new ArgumentOutOfRangeException(nameof(permits)); + } #if NET20 Monitor.Enter(lockObject); #else - await semaphoreSlim.WaitAsync(cancellationToken); + await this.semaphoreSlim.WaitAsync(cancellationToken); #endif try { - return ReserveAndGetWaitLength(permits, stopwatchProvider.GetTimestamp()); + return this.ReserveAndGetWaitLength(permits, this.StopwatchProvider.GetTimestamp()); } finally { #if NET20 Monitor.Exit(lockObject); #else - semaphoreSlim.Release(); + this.semaphoreSlim.Release(); #endif } } @@ -304,17 +313,17 @@ public async Task ReserveAsync(int permits, CancellationToken cancella protected TimeSpan ReserveAndGetWaitLength(int permits, long nowTimestamp) { - var momentAvailable = stopwatchProvider.ParseDuration( + var momentAvailable = this.StopwatchProvider.ParseDuration( nowTimestamp, - ReserveEarliestAvailable(permits, nowTimestamp)); + this.ReserveEarliestAvailable(permits, nowTimestamp)); return momentAvailable.Ticks > 0 ? momentAvailable : TimeSpan.Zero; } protected bool CanAcquire(long nowTimestamp, TimeSpan timeout, out TimeSpan momentAvailableInterval) { - momentAvailableInterval = stopwatchProvider.ParseDuration( + momentAvailableInterval = this.StopwatchProvider.ParseDuration( nowTimestamp, - QueryEarliestAvailable(nowTimestamp)); + this.QueryEarliestAvailable(nowTimestamp)); return momentAvailableInterval <= timeout; } @@ -323,4 +332,3 @@ protected bool CanAcquire(long nowTimestamp, TimeSpan timeout, out TimeSpan mome protected abstract long ReserveEarliestAvailable(int permits, long nowTimestamp); } } - diff --git a/RateLimiter/RateLimiter/SmoothBurstyRateLimiter.cs b/RateLimiter/RateLimiter/SmoothBurstyRateLimiter.cs index 3feb6180..12959ddd 100644 --- a/RateLimiter/RateLimiter/SmoothBurstyRateLimiter.cs +++ b/RateLimiter/RateLimiter/SmoothBurstyRateLimiter.cs @@ -10,6 +10,8 @@ namespace RateLimiter { internal sealed class SmoothBurstyRateLimiter : SmoothRateLimiter { + private readonly double maxBurstSeconds; + public SmoothBurstyRateLimiter( IStopwatchProvider stopwatchProvider, double maxBurstSeconds) @@ -30,21 +32,19 @@ internal SmoothBurstyRateLimiter( this.maxBurstSeconds = maxBurstSeconds; } - private readonly double maxBurstSeconds; - - protected override TimeSpan CoolDownInterval => stableInterval; + protected override TimeSpan CoolDownInterval => this.stableInterval; protected override void DoSetRate(double permitsPerSecond, TimeSpan stableInterval) { - var oldMaxPermits = maxPermits; - maxPermits = maxBurstSeconds * permitsPerSecond; + var oldMaxPermits = this.maxPermits; + this.maxPermits = this.maxBurstSeconds * permitsPerSecond; if (double.IsPositiveInfinity(oldMaxPermits)) { - storedPermits = maxPermits; + this.storedPermits = this.maxPermits; } else { - storedPermits = (oldMaxPermits == 0) ? 0 : storedPermits * maxPermits / oldMaxPermits; + this.storedPermits = (oldMaxPermits == 0) ? 0 : this.storedPermits * this.maxPermits / oldMaxPermits; } } @@ -54,4 +54,3 @@ protected override TimeSpan StoredPermitsToWaitTime(double storedPermits, double } } } - diff --git a/RateLimiter/RateLimiter/SmoothRateLimiter.cs b/RateLimiter/RateLimiter/SmoothRateLimiter.cs index 4eccb8ae..cf1c4892 100644 --- a/RateLimiter/RateLimiter/SmoothRateLimiter.cs +++ b/RateLimiter/RateLimiter/SmoothRateLimiter.cs @@ -10,6 +10,14 @@ namespace RateLimiter { public abstract class SmoothRateLimiter : RateLimiterBase { + protected double storedPermits; + + protected double maxPermits; + + protected TimeSpan stableInterval; + + private long nextFreeTicketTimestamp = 0; + protected SmoothRateLimiter(IStopwatchProvider stopwatchProvider) #if !NET20 : this(stopwatchProvider, null) @@ -24,69 +32,60 @@ internal SmoothRateLimiter(IStopwatchProvider stopwatchProvider, IAsyncBlo { } - protected double storedPermits; - - protected double maxPermits; - - protected TimeSpan stableInterval; - - private long nextFreeTicketTimestamp = 0; - protected abstract TimeSpan CoolDownInterval { get; } protected sealed override void DoSetRate(double permitsPerSecond, long nowTimestamp) { - Resync(nowTimestamp); + this.Resync(nowTimestamp); var stableIntervalSeconds = 1 / permitsPerSecond; var stableInterval = stableIntervalSeconds < TimeSpan.MaxValue.TotalSeconds ? TimeSpan.FromSeconds(1 / permitsPerSecond) : TimeSpan.MaxValue; this.stableInterval = stableInterval; - DoSetRate(permitsPerSecond, stableInterval); + this.DoSetRate(permitsPerSecond, stableInterval); } protected sealed override double DoGetRate() { - return 1 / stableInterval.TotalSeconds; + return 1 / this.stableInterval.TotalSeconds; } protected sealed override long QueryEarliestAvailable(long nowTimestamp) { - return nextFreeTicketTimestamp; + return this.nextFreeTicketTimestamp; } protected sealed override long ReserveEarliestAvailable(int requiredPermits, long nowTimestamp) { - Resync(nowTimestamp); - var returnValue = nextFreeTicketTimestamp; - var storedPermitsToSpend = Math.Min(requiredPermits, storedPermits); + this.Resync(nowTimestamp); + var returnValue = this.nextFreeTicketTimestamp; + var storedPermitsToSpend = Math.Min(requiredPermits, this.storedPermits); var freshPermits = requiredPermits - storedPermitsToSpend; - var waitTimeout = StoredPermitsToWaitTime(storedPermits, storedPermitsToSpend) - + FreshPermitsToWaitTime(freshPermits); - nextFreeTicketTimestamp = stopwatchProvider.GetNextTimestamp(nextFreeTicketTimestamp, waitTimeout); - storedPermits -= storedPermitsToSpend; + var waitTimeout = this.StoredPermitsToWaitTime(this.storedPermits, storedPermitsToSpend) + + this.FreshPermitsToWaitTime(freshPermits); + this.nextFreeTicketTimestamp = this.StopwatchProvider.GetNextTimestamp(this.nextFreeTicketTimestamp, waitTimeout); + this.storedPermits -= storedPermitsToSpend; return returnValue; } protected abstract TimeSpan StoredPermitsToWaitTime(double storedPermits, double permitsToTake); - private TimeSpan FreshPermitsToWaitTime(double permits) - { - return stableInterval.Multiply(permits); - } - protected void Resync(long nowTimestamp) { - if (nowTimestamp > nextFreeTicketTimestamp) + if (nowTimestamp > this.nextFreeTicketTimestamp) { - var newDuration = stopwatchProvider.ParseDuration(nextFreeTicketTimestamp, nowTimestamp); - double newPermits = newDuration.Ticks / (double)CoolDownInterval.Ticks; - storedPermits = Math.Min(maxPermits, storedPermits + newPermits); - nextFreeTicketTimestamp = nowTimestamp; + var newDuration = this.StopwatchProvider.ParseDuration(this.nextFreeTicketTimestamp, nowTimestamp); + double newPermits = newDuration.Ticks / (double)this.CoolDownInterval.Ticks; + this.storedPermits = Math.Min(this.maxPermits, this.storedPermits + newPermits); + this.nextFreeTicketTimestamp = nowTimestamp; } } protected abstract void DoSetRate(double permitsPerSecond, TimeSpan stableInterval); + + private TimeSpan FreshPermitsToWaitTime(double permits) + { + return this.stableInterval.Multiply(permits); + } } } - diff --git a/RateLimiter/RateLimiter/SmoothWarmingUpRateLimiter.cs b/RateLimiter/RateLimiter/SmoothWarmingUpRateLimiter.cs index 22cc4dad..3943f7a2 100644 --- a/RateLimiter/RateLimiter/SmoothWarmingUpRateLimiter.cs +++ b/RateLimiter/RateLimiter/SmoothWarmingUpRateLimiter.cs @@ -10,6 +10,14 @@ namespace RateLimiter { internal sealed class SmoothWarmingUpRateLimiter : SmoothRateLimiter { + private readonly TimeSpan warmupPeriod; + + private readonly double coldFactor; + + private double thresholdPermits; + + private TimeSpan slope; + public SmoothWarmingUpRateLimiter( IStopwatchProvider stopwatchProvider, TimeSpan warmupPeriod, @@ -33,53 +41,45 @@ internal SmoothWarmingUpRateLimiter( this.coldFactor = coldFactor; } - private readonly TimeSpan warmupPeriod; - - private readonly double coldFactor; - - private double thresholdPermits; - - private TimeSpan slope; - - protected override TimeSpan CoolDownInterval => warmupPeriod.Divide(maxPermits); + protected override TimeSpan CoolDownInterval => this.warmupPeriod.Divide(this.maxPermits); protected override void DoSetRate(double permitsPerSecond, TimeSpan stableInterval) { - var oldMaxPermits = maxPermits; - var coldInterval = stableInterval.Multiply(coldFactor); - thresholdPermits = 0.5 * warmupPeriod.Ticks / stableInterval.Ticks; - maxPermits = thresholdPermits + 2.0 * warmupPeriod.Ticks / (stableInterval + coldInterval).Ticks; - slope = (coldInterval - stableInterval).Divide(maxPermits - thresholdPermits); + var oldMaxPermits = this.maxPermits; + var coldInterval = stableInterval.Multiply(this.coldFactor); + this.thresholdPermits = 0.5 * this.warmupPeriod.Ticks / stableInterval.Ticks; + this.maxPermits = this.thresholdPermits + (2.0 * this.warmupPeriod.Ticks / (stableInterval + coldInterval).Ticks); + this.slope = (coldInterval - stableInterval).Divide(this.maxPermits - this.thresholdPermits); if (oldMaxPermits == double.PositiveInfinity) { - storedPermits = 0; + this.storedPermits = 0; } else { - storedPermits = (oldMaxPermits == 0) ? maxPermits : (storedPermits * maxPermits / oldMaxPermits); + this.storedPermits = (oldMaxPermits == 0) ? this.maxPermits : (this.storedPermits * this.maxPermits / oldMaxPermits); } } protected override TimeSpan StoredPermitsToWaitTime(double storedPermits, double permitsToTake) { - var availablePermitsAboveThreshold = storedPermits - thresholdPermits; + var availablePermitsAboveThreshold = storedPermits - this.thresholdPermits; var returnValue = TimeSpan.Zero; if (availablePermitsAboveThreshold > 0) { var permitsAboveThresholdToTake = Math.Min(availablePermitsAboveThreshold, permitsToTake); - var length = PermitsToTime(availablePermitsAboveThreshold) - + PermitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake); + var length = this.PermitsToTime(availablePermitsAboveThreshold) + + this.PermitsToTime(availablePermitsAboveThreshold - permitsAboveThresholdToTake); returnValue = length.Multiply(permitsAboveThresholdToTake / 2.0); permitsToTake -= permitsAboveThresholdToTake; } - returnValue += stableInterval.Multiply(permitsToTake); + + returnValue += this.stableInterval.Multiply(permitsToTake); return returnValue; } private TimeSpan PermitsToTime(double permits) { - return stableInterval + slope.Multiply(permits); + return this.stableInterval + this.slope.Multiply(permits); } } } - diff --git a/RateLimiter/RateLimiter/TimeSpanExtensions.cs b/RateLimiter/RateLimiter/TimeSpanExtensions.cs index fbad8142..5acf638f 100644 --- a/RateLimiter/RateLimiter/TimeSpanExtensions.cs +++ b/RateLimiter/RateLimiter/TimeSpanExtensions.cs @@ -36,4 +36,3 @@ public static TimeSpan Divide(this TimeSpan timeSpan, double divider) } } } - diff --git a/RateLimiter/RateLimiter/TryAcquireResult.cs b/RateLimiter/RateLimiter/TryAcquireResult.cs index d5104f03..cbb25e3c 100644 --- a/RateLimiter/RateLimiter/TryAcquireResult.cs +++ b/RateLimiter/RateLimiter/TryAcquireResult.cs @@ -14,4 +14,3 @@ public class TryAcquireResult public TimeSpan MomentAvailableInterval { get; set; } } } -