public async Task RetryMetadataOnFailedWaitAsync() { var options = new FixedWindowRateLimiterOptions { PermitLimit = 2, QueueProcessingOrder = QueueProcessingOrder.OldestFirst, QueueLimit = 1, Window = TimeSpan.FromSeconds(20), AutoReplenishment = false }; var limiter = new FixedWindowRateLimiter(options); using var lease = limiter.AttemptAcquire(2); var failedLease = await limiter.AcquireAsync(2); Assert.False(failedLease.IsAcquired); Assert.True(failedLease.TryGetMetadata(MetadataName.RetryAfter.Name, out var metadata)); var metaDataTime = Assert.IsType <TimeSpan>(metadata); Assert.Equal(options.Window.Ticks, metaDataTime.Ticks); Assert.True(failedLease.TryGetMetadata(MetadataName.RetryAfter, out var typedMetadata)); Assert.Equal(options.Window.Ticks, typedMetadata.Ticks); Assert.Collection(failedLease.MetadataNames, item => item.Equals(MetadataName.RetryAfter.Name)); }
public void Create_FixedWindow() { var options = new FixedWindowRateLimiterOptions(1, QueueProcessingOrder.OldestFirst, 10, TimeSpan.FromMinutes(1), true); var partition = RateLimitPartition.GetFixedWindowLimiter(1, key => options); var limiter = partition.Factory(1); var fixedWindowLimiter = Assert.IsType <FixedWindowRateLimiter>(limiter); Assert.Equal(options.PermitLimit, fixedWindowLimiter.GetAvailablePermits()); Assert.Equal(options.Window, fixedWindowLimiter.ReplenishmentPeriod); Assert.False(fixedWindowLimiter.IsAutoReplenishing); }
public async Task CorrectRetryMetadataWithNonZeroAvailableItems() { var options = new FixedWindowRateLimiterOptions(3, QueueProcessingOrder.OldestFirst, 1, TimeSpan.FromSeconds(20), autoReplenishment: false); var limiter = new FixedWindowRateLimiter(options); using var lease = limiter.Acquire(2); var failedLease = await limiter.WaitAsync(3); Assert.False(failedLease.IsAcquired); Assert.True(failedLease.TryGetMetadata(MetadataName.RetryAfter, out var typedMetadata)); Assert.Equal(options.Window.Ticks, typedMetadata.Ticks); }
/// <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> GetFixedWindowLimiter <TKey>( TKey partitionKey, Func <TKey, FixedWindowRateLimiterOptions> factory) { return(Get(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); })); }
public async Task CorrectRetryMetadataWithQueuedItem() { var options = new FixedWindowRateLimiterOptions(2, QueueProcessingOrder.OldestFirst, 1, TimeSpan.FromSeconds(20), autoReplenishment: false); var limiter = new FixedWindowRateLimiter(options); using var lease = limiter.Acquire(2); // Queue item which changes the retry after time for failed items var wait = limiter.WaitAsync(1); Assert.False(wait.IsCompleted); var failedLease = await limiter.WaitAsync(2); Assert.False(failedLease.IsAcquired); Assert.True(failedLease.TryGetMetadata(MetadataName.RetryAfter, out var typedMetadata)); Assert.Equal(options.Window.Ticks, typedMetadata.Ticks); }
/// <summary> /// Adds a new <see cref="FixedWindowRateLimiter"/> with the given <see cref="FixedWindowRateLimiterOptions"/> to the <see cref="RateLimiterOptions"/>. /// </summary> /// <param name="options">The <see cref="RateLimiterOptions"/> to add a limiter to.</param> /// <param name="policyName">The name that will be associated with the limiter.</param> /// <param name="configureOptions">A callback to configure the <see cref="FixedWindowRateLimiterOptions"/> to be used for the limiter.</param> /// <returns>This <see cref="RateLimiterOptions"/>.</returns> public static RateLimiterOptions AddFixedWindowLimiter(this RateLimiterOptions options, string policyName, Action <FixedWindowRateLimiterOptions> configureOptions) { ArgumentNullException.ThrowIfNull(configureOptions); var key = new PolicyNameKey() { PolicyName = policyName }; var fixedWindowRateLimiterOptions = new FixedWindowRateLimiterOptions(); configureOptions.Invoke(fixedWindowRateLimiterOptions); // Saves an allocation in GetFixedWindowLimiter, which would have created a new set of options if this was true. fixedWindowRateLimiterOptions.AutoReplenishment = false; return(options.AddPolicy(policyName, context => { return RateLimitPartition.GetFixedWindowLimiter(key, _ => fixedWindowRateLimiterOptions); })); }