diff --git a/src/libraries/System.Threading.RateLimiting/ref/System.Threading.RateLimiting.cs b/src/libraries/System.Threading.RateLimiting/ref/System.Threading.RateLimiting.cs index 9b3419ab6a2265..7c4f99e6167b13 100644 --- a/src/libraries/System.Threading.RateLimiting/ref/System.Threading.RateLimiting.cs +++ b/src/libraries/System.Threading.RateLimiting/ref/System.Threading.RateLimiting.cs @@ -23,6 +23,28 @@ public ConcurrencyLimiterOptions(int permitLimit, System.Threading.RateLimiting. public int QueueLimit { get { throw null; } } public System.Threading.RateLimiting.QueueProcessingOrder QueueProcessingOrder { get { throw null; } } } + public sealed partial class FixedWindowRateLimiter : System.Threading.RateLimiting.ReplenishingRateLimiter + { + public FixedWindowRateLimiter(System.Threading.RateLimiting.FixedWindowRateLimiterOptions options) { } + public override System.TimeSpan? IdleDuration { get { throw null; } } + public override bool IsAutoReplenishing { get { throw null; } } + public override System.TimeSpan ReplenishmentPeriod { get { throw null; } } + protected override System.Threading.RateLimiting.RateLimitLease AcquireCore(int requestCount) { throw null; } + protected override void Dispose(bool disposing) { } + protected override System.Threading.Tasks.ValueTask DisposeAsyncCore() { throw null; } + public override int GetAvailablePermits() { throw null; } + public override bool TryReplenish() { throw null; } + protected override System.Threading.Tasks.ValueTask WaitAsyncCore(int requestCount, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + } + public sealed partial class FixedWindowRateLimiterOptions + { + public FixedWindowRateLimiterOptions(int permitLimit, System.Threading.RateLimiting.QueueProcessingOrder queueProcessingOrder, int queueLimit, System.TimeSpan window, bool autoReplenishment = true) { } + public bool AutoReplenishment { get { throw null; } } + public int PermitLimit { get { throw null; } } + public int QueueLimit { get { throw null; } } + public System.Threading.RateLimiting.QueueProcessingOrder QueueProcessingOrder { get { throw null; } } + public System.TimeSpan Window { get { throw null; } } + } public static partial class MetadataName { public static System.Threading.RateLimiting.MetadataName ReasonPhrase { get { throw null; } } @@ -90,7 +112,9 @@ protected virtual void Dispose(bool disposing) { } public static partial class RateLimitPartition { public static System.Threading.RateLimiting.RateLimitPartition CreateConcurrencyLimiter(TKey partitionKey, System.Func factory) { throw null; } + public static System.Threading.RateLimiting.RateLimitPartition CreateFixedWindowLimiter(TKey partitionKey, System.Func factory) { throw null; } public static System.Threading.RateLimiting.RateLimitPartition CreateNoLimiter(TKey partitionKey) { throw null; } + public static System.Threading.RateLimiting.RateLimitPartition CreateSlidingWindowLimiter(TKey partitionKey, System.Func factory) { throw null; } public static System.Threading.RateLimiting.RateLimitPartition CreateTokenBucketLimiter(TKey partitionKey, System.Func factory) { throw null; } public static System.Threading.RateLimiting.RateLimitPartition Create(TKey partitionKey, System.Func factory) { throw null; } } @@ -109,29 +133,6 @@ protected ReplenishingRateLimiter() { } public abstract System.TimeSpan ReplenishmentPeriod { get; } public abstract bool TryReplenish(); } - public sealed partial class TokenBucketRateLimiter : System.Threading.RateLimiting.ReplenishingRateLimiter - { - public TokenBucketRateLimiter(System.Threading.RateLimiting.TokenBucketRateLimiterOptions options) { } - public override System.TimeSpan? IdleDuration { get { throw null; } } - public override bool IsAutoReplenishing { get { throw null; } } - public override System.TimeSpan ReplenishmentPeriod { get { throw null; } } - protected override System.Threading.RateLimiting.RateLimitLease AcquireCore(int tokenCount) { throw null; } - protected override void Dispose(bool disposing) { } - protected override System.Threading.Tasks.ValueTask DisposeAsyncCore() { throw null; } - public override int GetAvailablePermits() { throw null; } - public override bool TryReplenish() { throw null; } - protected override System.Threading.Tasks.ValueTask WaitAsyncCore(int tokenCount, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } - } - public sealed partial class TokenBucketRateLimiterOptions - { - public TokenBucketRateLimiterOptions(int tokenLimit, System.Threading.RateLimiting.QueueProcessingOrder queueProcessingOrder, int queueLimit, System.TimeSpan replenishmentPeriod, int tokensPerPeriod, bool autoReplenishment = true) { } - public bool AutoReplenishment { get { throw null; } } - public int QueueLimit { get { throw null; } } - public System.Threading.RateLimiting.QueueProcessingOrder QueueProcessingOrder { get { throw null; } } - public System.TimeSpan ReplenishmentPeriod { get { throw null; } } - public int TokenLimit { get { throw null; } } - public int TokensPerPeriod { get { throw null; } } - } public sealed partial class SlidingWindowRateLimiter : System.Threading.RateLimiting.ReplenishingRateLimiter { public SlidingWindowRateLimiter(System.Threading.RateLimiting.SlidingWindowRateLimiterOptions options) { } @@ -149,32 +150,33 @@ public sealed partial class SlidingWindowRateLimiterOptions { public SlidingWindowRateLimiterOptions(int permitLimit, System.Threading.RateLimiting.QueueProcessingOrder queueProcessingOrder, int queueLimit, System.TimeSpan window, int segmentsPerWindow, bool autoReplenishment = true) { } public bool AutoReplenishment { get { throw null; } } + public int PermitLimit { get { throw null; } } public int QueueLimit { get { throw null; } } public System.Threading.RateLimiting.QueueProcessingOrder QueueProcessingOrder { get { throw null; } } - public System.TimeSpan Window { get { throw null; } } - public int PermitLimit { get { throw null; } } public int SegmentsPerWindow { get { throw null; } } + public System.TimeSpan Window { get { throw null; } } } - public sealed partial class FixedWindowRateLimiter : System.Threading.RateLimiting.ReplenishingRateLimiter + public sealed partial class TokenBucketRateLimiter : System.Threading.RateLimiting.ReplenishingRateLimiter { - public FixedWindowRateLimiter(System.Threading.RateLimiting.FixedWindowRateLimiterOptions options) { } + public TokenBucketRateLimiter(System.Threading.RateLimiting.TokenBucketRateLimiterOptions options) { } public override System.TimeSpan? IdleDuration { get { throw null; } } public override bool IsAutoReplenishing { get { throw null; } } public override System.TimeSpan ReplenishmentPeriod { get { throw null; } } - protected override System.Threading.RateLimiting.RateLimitLease AcquireCore(int requestCount) { throw null; } + protected override System.Threading.RateLimiting.RateLimitLease AcquireCore(int tokenCount) { throw null; } protected override void Dispose(bool disposing) { } protected override System.Threading.Tasks.ValueTask DisposeAsyncCore() { throw null; } public override int GetAvailablePermits() { throw null; } public override bool TryReplenish() { throw null; } - protected override System.Threading.Tasks.ValueTask WaitAsyncCore(int requestCount, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } + protected override System.Threading.Tasks.ValueTask WaitAsyncCore(int tokenCount, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; } } - public sealed partial class FixedWindowRateLimiterOptions + public sealed partial class TokenBucketRateLimiterOptions { - public FixedWindowRateLimiterOptions(int permitLimit, System.Threading.RateLimiting.QueueProcessingOrder queueProcessingOrder, int queueLimit, System.TimeSpan window, bool autoReplenishment = true) { } + public TokenBucketRateLimiterOptions(int tokenLimit, System.Threading.RateLimiting.QueueProcessingOrder queueProcessingOrder, int queueLimit, System.TimeSpan replenishmentPeriod, int tokensPerPeriod, bool autoReplenishment = true) { } public bool AutoReplenishment { get { throw null; } } public int QueueLimit { get { throw null; } } public System.Threading.RateLimiting.QueueProcessingOrder QueueProcessingOrder { get { throw null; } } - public System.TimeSpan Window { get { throw null; } } - public int PermitLimit { get { throw null; } } + public System.TimeSpan ReplenishmentPeriod { get { throw null; } } + public int TokenLimit { get { throw null; } } + public int TokensPerPeriod { get { throw null; } } } } diff --git a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/RateLimitPartition.cs b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/RateLimitPartition.cs index 134794940cf585..1942262ba95706 100644 --- a/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/RateLimitPartition.cs +++ b/src/libraries/System.Threading.RateLimiting/src/System/Threading/RateLimiting/RateLimitPartition.cs @@ -12,6 +12,9 @@ public static class RateLimitPartition /// Defines a partition with the given rate limiter factory. /// /// The type to distinguish partitions with. + /// + /// The should return a new instance of a rate limiter every time it is called. + /// /// The specific key for this partition. This will be used to check for an existing cached limiter before calling the . /// The function called when a rate limiter for the given is needed. This should be a new instance of a rate limiter every time it is called. /// @@ -74,5 +77,59 @@ public static RateLimitPartition CreateTokenBucketLimiter( return new TokenBucketRateLimiter(options); }); } + + /// + /// Defines a partition with a with the given . + /// + /// + /// Set to to save an allocation. This method will create a new options type and set to otherwise. + /// + /// The type to distinguish partitions with. + /// The specific key for this partition. + /// The function called when a rate limiter for the given is needed. This can return the same instance of across different calls. + /// + public static RateLimitPartition CreateSlidingWindowLimiter( + TKey partitionKey, + Func factory) + { + return Create(partitionKey, key => + { + SlidingWindowRateLimiterOptions options = factory(key); + // We don't want individual SlidingWindowRateLimiters to have timers. We will instead have our own internal Timer handling all of them + if (options.AutoReplenishment is true) + { + options = new SlidingWindowRateLimiterOptions(options.PermitLimit, options.QueueProcessingOrder, options.QueueLimit, + options.Window, options.SegmentsPerWindow, autoReplenishment: false); + } + return new SlidingWindowRateLimiter(options); + }); + } + + /// + /// Defines a partition with a with the given . + /// + /// + /// Set to to save an allocation. This method will create a new options type and set to otherwise. + /// + /// The type to distinguish partitions with. + /// The specific key for this partition. + /// The function called when a rate limiter for the given is needed. This can return the same instance of across different calls. + /// + public static RateLimitPartition CreateFixedWindowLimiter( + TKey partitionKey, + Func factory) + { + return Create(partitionKey, key => + { + FixedWindowRateLimiterOptions options = factory(key); + // We don't want individual FixedWindowRateLimiters to have timers. We will instead have our own internal Timer handling all of them + if (options.AutoReplenishment is true) + { + options = new FixedWindowRateLimiterOptions(options.PermitLimit, options.QueueProcessingOrder, options.QueueLimit, + options.Window, autoReplenishment: false); + } + return new FixedWindowRateLimiter(options); + }); + } } } diff --git a/src/libraries/System.Threading.RateLimiting/tests/RateLimiterPartitionTests.cs b/src/libraries/System.Threading.RateLimiting/tests/RateLimiterPartitionTests.cs index 7e2bfe17285702..175c4e96fdcda1 100644 --- a/src/libraries/System.Threading.RateLimiting/tests/RateLimiterPartitionTests.cs +++ b/src/libraries/System.Threading.RateLimiting/tests/RateLimiterPartitionTests.cs @@ -32,8 +32,8 @@ public void Create_TokenBucket() var limiter = factory(1); var tokenBucketLimiter = Assert.IsType(limiter); Assert.Equal(options.TokenLimit, tokenBucketLimiter.GetAvailablePermits()); - // TODO: Check other properties when ReplenshingRateLimiter is merged - // TODO: Check that autoReplenishment: true got changed to false + Assert.Equal(options.ReplenishmentPeriod, tokenBucketLimiter.ReplenishmentPeriod); + Assert.False(tokenBucketLimiter.IsAutoReplenishing); } [Fact] @@ -79,5 +79,35 @@ public void Create_AnyLimiter() var tokenBucketLimiter = Assert.IsType(limiter); Assert.Equal(1, tokenBucketLimiter.GetAvailablePermits()); } + + [Fact] + public void Create_FixedWindow() + { + var options = new FixedWindowRateLimiterOptions(1, QueueProcessingOrder.OldestFirst, 10, TimeSpan.FromMinutes(1), true); + var partition = RateLimitPartition.CreateFixedWindowLimiter(1, key => options); + + var factoryProperty = typeof(RateLimitPartition).GetField("Factory", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var factory = (Func)factoryProperty.GetValue(partition); + var limiter = factory(1); + var fixedWindowLimiter = Assert.IsType(limiter); + Assert.Equal(options.PermitLimit, fixedWindowLimiter.GetAvailablePermits()); + Assert.Equal(options.Window, fixedWindowLimiter.ReplenishmentPeriod); + Assert.False(fixedWindowLimiter.IsAutoReplenishing); + } + + [Fact] + public void Create_SlidingWindow() + { + var options = new SlidingWindowRateLimiterOptions(1, QueueProcessingOrder.OldestFirst, 10, TimeSpan.FromSeconds(33), 3, true); + var partition = RateLimitPartition.CreateSlidingWindowLimiter(1, key => options); + + var factoryProperty = typeof(RateLimitPartition).GetField("Factory", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!; + var factory = (Func)factoryProperty.GetValue(partition); + var limiter = factory(1); + var slidingWindowLimiter = Assert.IsType(limiter); + Assert.Equal(options.PermitLimit, slidingWindowLimiter.GetAvailablePermits()); + Assert.Equal(TimeSpan.FromSeconds(11), slidingWindowLimiter.ReplenishmentPeriod); + Assert.False(slidingWindowLimiter.IsAutoReplenishing); + } } }