Skip to content

Commit

Permalink
Add SlidingWindow and FixedWindow to RateLimitPartition (#68782)
Browse files Browse the repository at this point in the history
  • Loading branch information
BrennanConroy authored Jun 14, 2022
1 parent 89962a5 commit 8bb880d
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 35 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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<System.Threading.RateLimiting.RateLimitLease> 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<string> ReasonPhrase { get { throw null; } }
Expand Down Expand Up @@ -90,7 +112,9 @@ protected virtual void Dispose(bool disposing) { }
public static partial class RateLimitPartition
{
public static System.Threading.RateLimiting.RateLimitPartition<TKey> CreateConcurrencyLimiter<TKey>(TKey partitionKey, System.Func<TKey, System.Threading.RateLimiting.ConcurrencyLimiterOptions> factory) { throw null; }
public static System.Threading.RateLimiting.RateLimitPartition<TKey> CreateFixedWindowLimiter<TKey>(TKey partitionKey, System.Func<TKey, System.Threading.RateLimiting.FixedWindowRateLimiterOptions> factory) { throw null; }
public static System.Threading.RateLimiting.RateLimitPartition<TKey> CreateNoLimiter<TKey>(TKey partitionKey) { throw null; }
public static System.Threading.RateLimiting.RateLimitPartition<TKey> CreateSlidingWindowLimiter<TKey>(TKey partitionKey, System.Func<TKey, System.Threading.RateLimiting.SlidingWindowRateLimiterOptions> factory) { throw null; }
public static System.Threading.RateLimiting.RateLimitPartition<TKey> CreateTokenBucketLimiter<TKey>(TKey partitionKey, System.Func<TKey, System.Threading.RateLimiting.TokenBucketRateLimiterOptions> factory) { throw null; }
public static System.Threading.RateLimiting.RateLimitPartition<TKey> Create<TKey>(TKey partitionKey, System.Func<TKey, System.Threading.RateLimiting.RateLimiter> factory) { throw null; }
}
Expand All @@ -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<System.Threading.RateLimiting.RateLimitLease> 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) { }
Expand All @@ -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<System.Threading.RateLimiting.RateLimitLease> WaitAsyncCore(int requestCount, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) { throw null; }
protected override System.Threading.Tasks.ValueTask<System.Threading.RateLimiting.RateLimitLease> 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; } }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ public static class RateLimitPartition
/// Defines a partition with the given rate limiter factory.
/// </summary>
/// <typeparam name="TKey">The type to distinguish partitions with.</typeparam>
/// <remarks>
/// The <paramref name="factory"/> should return a new instance of a rate limiter every time it is called.
/// </remarks>
/// <param name="partitionKey">The specific key for this partition. This will be used to check for an existing cached limiter before calling the <paramref name="factory"/>.</param>
/// <param name="factory">The function called when a rate limiter for the given <paramref name="partitionKey"/> is needed. This should be a new instance of a rate limiter every time it is called.</param>
/// <returns></returns>
Expand Down Expand Up @@ -74,5 +77,59 @@ public static RateLimitPartition<TKey> CreateTokenBucketLimiter<TKey>(
return new TokenBucketRateLimiter(options);
});
}

/// <summary>
/// Defines a partition with a <see cref="SlidingWindowRateLimiter"/> with the given <see cref="SlidingWindowRateLimiterOptions"/>.
/// </summary>
/// <remarks>
/// Set <see cref="SlidingWindowRateLimiterOptions.AutoReplenishment"/> to <see langword="false"/> to save an allocation. This method will create a new options type and set <see cref="SlidingWindowRateLimiterOptions.AutoReplenishment"/> to <see langword="false"/> otherwise.
/// </remarks>
/// <typeparam name="TKey">The type to distinguish partitions with.</typeparam>
/// <param name="partitionKey">The specific key for this partition.</param>
/// <param name="factory">The function called when a rate limiter for the given <paramref name="partitionKey"/> is needed. This can return the same instance of <see cref="SlidingWindowRateLimiterOptions"/> across different calls.</param>
/// <returns></returns>
public static RateLimitPartition<TKey> CreateSlidingWindowLimiter<TKey>(
TKey partitionKey,
Func<TKey, SlidingWindowRateLimiterOptions> 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);
});
}

/// <summary>
/// Defines a partition with a <see cref="FixedWindowRateLimiter"/> with the given <see cref="FixedWindowRateLimiterOptions"/>.
/// </summary>
/// <remarks>
/// Set <see cref="FixedWindowRateLimiterOptions.AutoReplenishment"/> to <see langword="false"/> to save an allocation. This method will create a new options type and set <see cref="FixedWindowRateLimiterOptions.AutoReplenishment"/> to <see langword="false"/> otherwise.
/// </remarks>
/// <typeparam name="TKey">The type to distinguish partitions with.</typeparam>
/// <param name="partitionKey">The specific key for this partition.</param>
/// <param name="factory">The function called when a rate limiter for the given <paramref name="partitionKey"/> is needed. This can return the same instance of <see cref="FixedWindowRateLimiterOptions"/> across different calls.</param>
/// <returns></returns>
public static RateLimitPartition<TKey> CreateFixedWindowLimiter<TKey>(
TKey partitionKey,
Func<TKey, FixedWindowRateLimiterOptions> 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);
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,8 @@ public void Create_TokenBucket()
var limiter = factory(1);
var tokenBucketLimiter = Assert.IsType<TokenBucketRateLimiter>(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]
Expand Down Expand Up @@ -79,5 +79,35 @@ public void Create_AnyLimiter()
var tokenBucketLimiter = Assert.IsType<TokenBucketRateLimiter>(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<int>).GetField("Factory", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!;
var factory = (Func<int, RateLimiter>)factoryProperty.GetValue(partition);
var limiter = factory(1);
var fixedWindowLimiter = Assert.IsType<FixedWindowRateLimiter>(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<int>).GetField("Factory", Reflection.BindingFlags.NonPublic | Reflection.BindingFlags.Instance)!;
var factory = (Func<int, RateLimiter>)factoryProperty.GetValue(partition);
var limiter = factory(1);
var slidingWindowLimiter = Assert.IsType<SlidingWindowRateLimiter>(limiter);
Assert.Equal(options.PermitLimit, slidingWindowLimiter.GetAvailablePermits());
Assert.Equal(TimeSpan.FromSeconds(11), slidingWindowLimiter.ReplenishmentPeriod);
Assert.False(slidingWindowLimiter.IsAutoReplenishing);
}
}
}

0 comments on commit 8bb880d

Please sign in to comment.