Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add SlidingWindow and FixedWindow to RateLimitPartition #68782

Merged
merged 1 commit into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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);
}
}
}