public async Task Create_WithReplenishingLimiterReplenishesAutomatically() { using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { // Use the non-specific Create method to make sure ReplenishingRateLimiters are still handled properly return(RateLimitPartition.Get(1, _ => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions { TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 1, ReplenishmentPeriod = TimeSpan.FromMilliseconds(100), TokensPerPeriod = 1, AutoReplenishment = false }))); }); var lease = limiter.AttemptAcquire(""); Assert.True(lease.IsAcquired); lease = await limiter.AcquireAsync(""); Assert.True(lease.IsAcquired); }
public void Create_PassedInEqualityComparerIsUsed() { var limiterFactory = new TrackingRateLimiterFactory <int>(); var equality = new TestEquality(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { if (resource == "1") { return(RateLimitPartition.Get(1, key => limiterFactory.GetLimiter(key))); } return(RateLimitPartition.Get(2, key => limiterFactory.GetLimiter(key))); }, equality); limiter.Acquire("1"); // GetHashCode to add item to dictionary (skips TryGet for empty dictionary) Assert.Equal(0, equality.EqualsCallCount); Assert.Equal(1, equality.GetHashCodeCallCount); limiter.Acquire("1"); // GetHashCode and Equal from TryGet to see if item is in dictionary Assert.Equal(1, equality.EqualsCallCount); Assert.Equal(2, equality.GetHashCodeCallCount); limiter.Acquire("2"); // GetHashCode from TryGet (fails check) and second GetHashCode to add item to dictionary Assert.Equal(1, equality.EqualsCallCount); Assert.Equal(4, equality.GetHashCodeCallCount); Assert.Equal(2, limiterFactory.Limiters.Count); Assert.Equal(2, limiterFactory.Limiters[0].Limiter.AcquireCallCount); Assert.Equal(1, limiterFactory.Limiters[1].Limiter.AcquireCallCount); }
public async Task Create_MultipleReplenishingLimitersReplenishAutomatically() { using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { if (resource == "1") { return(RateLimitPartition.GetTokenBucketLimiter(1, _ => new TokenBucketRateLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1, TimeSpan.FromMilliseconds(100), 1, false))); } return(RateLimitPartition.GetTokenBucketLimiter(2, _ => new TokenBucketRateLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1, TimeSpan.FromMilliseconds(100), 1, false))); }); var lease = limiter.Acquire("1"); Assert.True(lease.IsAcquired); lease = await limiter.WaitAndAcquireAsync("1"); Assert.True(lease.IsAcquired); // Creates the second Replenishing limiter // Indirectly tests that the cached list of limiters used by the timer is probably updated by making sure a limiter already made use of it before we create a second replenishing one lease = limiter.Acquire("2"); Assert.True(lease.IsAcquired); lease = await limiter.WaitAndAcquireAsync("1"); Assert.True(lease.IsAcquired); lease = await limiter.WaitAndAcquireAsync("2"); Assert.True(lease.IsAcquired); }
public async Task Create_BlockingWaitDoesNotBlockOtherPartitions() { var limiterFactory = new TrackingRateLimiterFactory <int>(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { if (resource == "1") { return(RateLimitPartition.Get(1, key => limiterFactory.GetLimiter(key))); } return(RateLimitPartition.GetConcurrencyLimiter(2, _ => new ConcurrencyLimiterOptions(1, QueueProcessingOrder.OldestFirst, 2))); }); var lease = await limiter.WaitAndAcquireAsync("2"); var wait = limiter.WaitAndAcquireAsync("2"); Assert.False(wait.IsCompleted); // Different partition, should not be blocked by the wait in the other partition await limiter.WaitAndAcquireAsync("1"); lease.Dispose(); await wait; Assert.Equal(1, limiterFactory.Limiters.Count); Assert.Equal(0, limiterFactory.Limiters[0].Limiter.AcquireCallCount); Assert.Equal(1, limiterFactory.Limiters[0].Limiter.WaitAndAcquireAsyncCallCount); }
public async Task IdleLimiterIsCleanedUp() { CustomizableLimiter innerLimiter = null; var factoryCallCount = 0; using var limiter = Utils.CreatePartitionedLimiterWithoutTimer <string, int>(resource => { return(RateLimitPartition.Get(1, _ => { factoryCallCount++; innerLimiter = new CustomizableLimiter(); return innerLimiter; })); }); var lease = limiter.Acquire(""); Assert.True(lease.IsAcquired); Assert.Equal(1, factoryCallCount); var tcs = new TaskCompletionSource <object?>(TaskCreationOptions.RunContinuationsAsynchronously); innerLimiter.DisposeAsyncCoreImpl = () => { tcs.SetResult(null); return(default);
public void Create_AnyLimiter() { var partition = RateLimitPartition.Get(1, key => new ConcurrencyLimiter(new ConcurrencyLimiterOptions { PermitLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 10 })); var limiter = partition.Factory(1); var concurrencyLimiter = Assert.IsType <ConcurrencyLimiter>(limiter); Assert.Equal(1, concurrencyLimiter.GetStatistics().CurrentAvailablePermits); var partition2 = RateLimitPartition.Get(1, key => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions { TokenLimit = 1, QueueProcessingOrder = QueueProcessingOrder.NewestFirst, QueueLimit = 10, ReplenishmentPeriod = TimeSpan.FromMilliseconds(100), TokensPerPeriod = 1, AutoReplenishment = false })); limiter = partition2.Factory(1); var tokenBucketLimiter = Assert.IsType <TokenBucketRateLimiter>(limiter); Assert.Equal(1, tokenBucketLimiter.GetStatistics().CurrentAvailablePermits); }
public void Create_MultiplePartitionsWork() { var limiterFactory = new TrackingRateLimiterFactory <int>(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { if (resource == "1") { return(RateLimitPartition.Get(1, key => limiterFactory.GetLimiter(key))); } else { return(RateLimitPartition.Get(2, key => limiterFactory.GetLimiter(key))); } }); limiter.Acquire("1"); limiter.Acquire("2"); limiter.Acquire("1"); limiter.Acquire("2"); Assert.Equal(2, limiterFactory.Limiters.Count); Assert.Equal(2, limiterFactory.Limiters[0].Limiter.AcquireCallCount); Assert.Equal(1, limiterFactory.Limiters[0].Key); Assert.Equal(2, limiterFactory.Limiters[1].Limiter.AcquireCallCount); Assert.Equal(2, limiterFactory.Limiters[1].Key); }
// Create the endpoint-specific PartitionedRateLimiter private PartitionedRateLimiter <HttpContext> CreateEndpointLimiter() { // If we have a policy for this endpoint, use its partitioner. Else use a NoLimiter. return(PartitionedRateLimiter.Create <HttpContext, DefaultKeyType>(context => { DefaultRateLimiterPolicy?policy; var enableRateLimitingAttribute = context.GetEndpoint()?.Metadata.GetMetadata <EnableRateLimitingAttribute>(); if (enableRateLimitingAttribute is null) { return RateLimitPartition.GetNoLimiter <DefaultKeyType>(_defaultPolicyKey); } policy = enableRateLimitingAttribute.Policy; if (policy is not null) { return policy.GetPartition(context); } var name = enableRateLimitingAttribute.PolicyName; if (name is not null) { if (_policyMap.TryGetValue(name, out policy)) { return policy.GetPartition(context); } else { throw new InvalidOperationException($"This endpoint requires a rate limiting policy with name {name}, but no such policy exists."); } } // Should be impossible for both name & policy to be null, but throw in that scenario just in case. else { throw new InvalidOperationException("This endpoint requested a rate limiting policy with a null name."); } }, new DefaultKeyTypeEqualityComparer())); }
public async Task Create_NoLimiter() { var partition = RateLimitPartition.CreateNoLimiter(1); 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); // How do we test an internal implementation of a limiter that doesn't limit? Just try some stuff that normal limiters would probably block on and see if it works. var available = limiter.GetAvailablePermits(); var lease = limiter.Acquire(int.MaxValue); Assert.True(lease.IsAcquired); Assert.Equal(available, limiter.GetAvailablePermits()); lease = limiter.Acquire(int.MaxValue); Assert.True(lease.IsAcquired); var wait = limiter.WaitAsync(int.MaxValue); Assert.True(wait.IsCompletedSuccessfully); lease = await wait; Assert.True(lease.IsAcquired); lease.Dispose(); }
public async Task Create_DisposeAsyncDisposesAllLimiters() { var limiterFactory = new TrackingRateLimiterFactory <int>(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { if (resource == "1") { return(RateLimitPartition.Get(1, key => limiterFactory.GetLimiter(key))); } return(RateLimitPartition.Get(2, key => limiterFactory.GetLimiter(key))); }); limiter.Acquire("1"); limiter.Acquire("2"); await limiter.DisposeAsync(); Assert.Equal(2, limiterFactory.Limiters.Count); Assert.Equal(1, limiterFactory.Limiters[0].Limiter.AcquireCallCount); Assert.Equal(1, limiterFactory.Limiters[0].Limiter.DisposeCallCount); Assert.Equal(1, limiterFactory.Limiters[0].Limiter.DisposeAsyncCallCount); Assert.Equal(1, limiterFactory.Limiters[1].Limiter.AcquireCallCount); Assert.Equal(1, limiterFactory.Limiters[1].Limiter.DisposeCallCount); Assert.Equal(1, limiterFactory.Limiters[1].Limiter.DisposeAsyncCallCount); }
public void AddPolicy_Generic_ThrowsOnDuplicateName() { var options = new RateLimiterOptions(); options.AddPolicy <string>("myKey", context => RateLimitPartition.GetNoLimiter <string>("myKey")); Assert.Throws <ArgumentException>(() => options.AddPolicy <string, TestRateLimiterPolicy>("myKey")); }
// Converts a Partition<TKey> to a Partition<DefaultKeyType<TKey>> to prevent accidental collisions with the keys we create in the the RateLimiterOptionsExtensions. internal static Func <HttpContext, RateLimitPartition <DefaultKeyType> > ConvertPartitioner <TPartitionKey>(string?policyName, Func <HttpContext, RateLimitPartition <TPartitionKey> > partitioner) { return(context => { RateLimitPartition <TPartitionKey> partition = partitioner(context); var partitionKey = new DefaultKeyType(policyName, partition.PartitionKey, partition.Factory); return new RateLimitPartition <DefaultKeyType>(partitionKey, static key => ((Func <TPartitionKey, RateLimiter>)key.Factory !)((TPartitionKey)key.Key !)); }); }
public void Create_Concurrency() { var options = new ConcurrencyLimiterOptions(10, QueueProcessingOrder.OldestFirst, 10); var partition = RateLimitPartition.GetConcurrencyLimiter(1, key => options); var limiter = partition.Factory(1); var concurrencyLimiter = Assert.IsType <ConcurrencyLimiter>(limiter); Assert.Equal(options.PermitLimit, concurrencyLimiter.GetAvailablePermits()); }
public void Create_Concurrency() { var options = new ConcurrencyLimiterOptions(10, QueueProcessingOrder.OldestFirst, 10); var partition = RateLimitPartition.CreateConcurrencyLimiter(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 concurrencyLimiter = Assert.IsType <ConcurrencyLimiter>(limiter); Assert.Equal(options.PermitLimit, concurrencyLimiter.GetAvailablePermits()); }
public void Create_SlidingWindow() { var options = new SlidingWindowRateLimiterOptions(1, QueueProcessingOrder.OldestFirst, 10, TimeSpan.FromSeconds(33), 3, true); var partition = RateLimitPartition.GetSlidingWindowLimiter(1, key => options); var limiter = partition.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); }
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 void Create_TokenBucket() { var options = new TokenBucketRateLimiterOptions(1, QueueProcessingOrder.OldestFirst, 10, TimeSpan.FromMinutes(1), 1, true); var partition = RateLimitPartition.GetTokenBucketLimiter(1, key => options); var limiter = partition.Factory(1); var tokenBucketLimiter = Assert.IsType <TokenBucketRateLimiter>(limiter); Assert.Equal(options.TokenLimit, tokenBucketLimiter.GetAvailablePermits()); Assert.Equal(options.ReplenishmentPeriod, tokenBucketLimiter.ReplenishmentPeriod); Assert.False(tokenBucketLimiter.IsAutoReplenishing); }
public void Create_GetAvailablePermitsCallsUnderlyingPartitionsLimiter() { var limiterFactory = new TrackingRateLimiterFactory <int>(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { return(RateLimitPartition.Get(1, key => limiterFactory.GetLimiter(key))); }); limiter.GetAvailablePermits(""); Assert.Equal(1, limiterFactory.Limiters.Count); Assert.Equal(1, limiterFactory.Limiters[0].Limiter.GetAvailablePermitsCallCount); }
public void Create_DisposeWithoutLimitersNoops() { var limiterFactory = new TrackingRateLimiterFactory <int>(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { return(RateLimitPartition.Get(1, key => limiterFactory.GetLimiter(key))); }); limiter.Dispose(); Assert.Equal(0, limiterFactory.Limiters.Count); }
public void Create_TokenBucket() { var options = new TokenBucketRateLimiterOptions(1, QueueProcessingOrder.OldestFirst, 10, TimeSpan.FromMinutes(1), 1, true); var partition = RateLimitPartition.CreateTokenBucketLimiter(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 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 }
public async Task Create_WaitAsyncCallsUnderlyingPartitionsLimiter() { var limiterFactory = new TrackingRateLimiterFactory <int>(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { return(RateLimitPartition.Get(1, key => limiterFactory.GetLimiter(key))); }); await limiter.WaitAndAcquireAsync(""); Assert.Equal(1, limiterFactory.Limiters.Count); Assert.Equal(1, limiterFactory.Limiters[0].Limiter.WaitAndAcquireAsyncCallCount); }
public async Task Create_BlockingFactoryDoesNotBlockOtherPartitions() { var limiterFactory = new TrackingRateLimiterFactory <int>(); var tcs = new TaskCompletionSource <object?>(TaskCreationOptions.RunContinuationsAsynchronously); var startedTcs = new TaskCompletionSource <object?>(TaskCreationOptions.RunContinuationsAsynchronously); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { if (resource == "1") { return(RateLimitPartition.Get(1, key => { startedTcs.SetResult(null); // block the factory method Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(10))); return limiterFactory.GetLimiter(key); })); } return(RateLimitPartition.Get(2, key => limiterFactory.GetLimiter(key))); }); var lease = await limiter.WaitAndAcquireAsync("2"); var blockedTask = Task.Run(async() => { await limiter.WaitAndAcquireAsync("1"); }); await startedTcs.Task; // Other partitions aren't blocked await limiter.WaitAndAcquireAsync("2"); // Try to acquire from the blocking limiter, this should wait until the blocking limiter has been resolved and not create a new one var blockedTask2 = Task.Run(async() => { await limiter.WaitAndAcquireAsync("1"); }); // unblock limiter factory tcs.SetResult(null); await blockedTask; await blockedTask2; // Only 2 limiters should have been created Assert.Equal(2, limiterFactory.Limiters.Count); Assert.Equal(2, limiterFactory.Limiters[0].Limiter.WaitAndAcquireAsyncCallCount); Assert.Equal(2, limiterFactory.Limiters[1].Limiter.WaitAndAcquireAsyncCallCount); }
public void Create_DisposeThrowsForFutureMethodCalls() { var limiterFactory = new TrackingRateLimiterFactory <int>(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { return(RateLimitPartition.Get(1, key => limiterFactory.GetLimiter(key))); }); limiter.Dispose(); Assert.Throws <ObjectDisposedException>(() => limiter.Acquire("1")); Assert.Equal(0, limiterFactory.Limiters.Count); }
public async Task Create_WithTokenBucketReplenishesAutomatically() { using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { return(RateLimitPartition.GetTokenBucketLimiter(1, _ => new TokenBucketRateLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1, TimeSpan.FromMilliseconds(100), 1, false))); }); var lease = limiter.Acquire(""); Assert.True(lease.IsAcquired); lease = await limiter.WaitAndAcquireAsync(""); Assert.True(lease.IsAcquired); }
public void Create_AnyLimiter() { var partition = RateLimitPartition.Get(1, key => new ConcurrencyLimiter(new ConcurrencyLimiterOptions(1, QueueProcessingOrder.NewestFirst, 10))); var limiter = partition.Factory(1); var concurrencyLimiter = Assert.IsType <ConcurrencyLimiter>(limiter); Assert.Equal(1, concurrencyLimiter.GetAvailablePermits()); var partition2 = RateLimitPartition.Get(1, key => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions(1, QueueProcessingOrder.NewestFirst, 10, TimeSpan.FromMilliseconds(100), 1, autoReplenishment: false))); limiter = partition2.Factory(1); var tokenBucketLimiter = Assert.IsType <TokenBucketRateLimiter>(limiter); Assert.Equal(1, tokenBucketLimiter.GetAvailablePermits()); }
/// <summary> /// Adds a new <see cref="ConcurrencyLimiter"/> with the given <see cref="ConcurrencyLimiterOptions"/> 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="ConcurrencyLimiterOptions"/> to be used for the limiter.</param> /// <returns>This <see cref="RateLimiterOptions"/>.</returns> public static RateLimiterOptions AddConcurrencyLimiter(this RateLimiterOptions options, string policyName, Action <ConcurrencyLimiterOptions> configureOptions) { ArgumentNullException.ThrowIfNull(configureOptions); var key = new PolicyNameKey() { PolicyName = policyName }; var concurrencyLimiterOptions = new ConcurrencyLimiterOptions(); configureOptions.Invoke(concurrencyLimiterOptions); return(options.AddPolicy(policyName, context => { return RateLimitPartition.GetConcurrencyLimiter(key, _ => concurrencyLimiterOptions); })); }
public async Task NoLimiter_GetStatistics() { var partition = RateLimitPartition.GetNoLimiter(1); var limiter = partition.Factory(1); var stats = limiter.GetStatistics(); Assert.NotSame(stats, limiter.GetStatistics()); Assert.Equal(long.MaxValue, stats.CurrentAvailablePermits); Assert.Equal(0, stats.CurrentQueuedCount); Assert.Equal(0, stats.TotalFailedLeases); Assert.Equal(0, stats.TotalSuccessfulLeases); var leaseCount = 0; for (var i = 0; i < 134; i++) { var lease = limiter.AttemptAcquire(i); Assert.True(lease.IsAcquired); ++leaseCount; } stats = limiter.GetStatistics(); Assert.Equal(long.MaxValue, stats.CurrentAvailablePermits); Assert.Equal(0, stats.CurrentQueuedCount); Assert.Equal(0, stats.TotalFailedLeases); Assert.Equal(leaseCount, stats.TotalSuccessfulLeases); for (var i = 0; i < 165; i++) { var wait = limiter.AcquireAsync(int.MaxValue); Assert.True(wait.IsCompletedSuccessfully); var lease = await wait; Assert.True(lease.IsAcquired); ++leaseCount; } stats = limiter.GetStatistics(); Assert.Equal(long.MaxValue, stats.CurrentAvailablePermits); Assert.Equal(0, stats.CurrentQueuedCount); Assert.Equal(0, stats.TotalFailedLeases); Assert.Equal(leaseCount, stats.TotalSuccessfulLeases); }
public async Task Create_PartitionIsCached() { var limiterFactory = new TrackingRateLimiterFactory <int>(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { return(RateLimitPartition.Create(1, key => limiterFactory.GetLimiter(key))); }); limiter.Acquire(""); await limiter.WaitAsync(""); limiter.Acquire(""); await limiter.WaitAsync(""); Assert.Equal(1, limiterFactory.Limiters.Count); Assert.Equal(2, limiterFactory.Limiters[0].Limiter.AcquireCallCount); Assert.Equal(2, limiterFactory.Limiters[0].Limiter.WaitAsyncCallCount); }
public void Create_AnyLimiter() { var partition = RateLimitPartition.Create(1, key => new ConcurrencyLimiter(new ConcurrencyLimiterOptions(1, QueueProcessingOrder.NewestFirst, 10))); 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 concurrencyLimiter = Assert.IsType <ConcurrencyLimiter>(limiter); Assert.Equal(1, concurrencyLimiter.GetAvailablePermits()); var partition2 = RateLimitPartition.Create(1, key => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions(1, QueueProcessingOrder.NewestFirst, 10, TimeSpan.FromMilliseconds(100), 1, autoReplenishment: false))); factory = (Func <int, RateLimiter>)factoryProperty.GetValue(partition2); limiter = factory(1); var tokenBucketLimiter = Assert.IsType <TokenBucketRateLimiter>(limiter); Assert.Equal(1, tokenBucketLimiter.GetAvailablePermits()); }
public async Task Create_CancellationTokenPassedToUnderlyingLimiter() { using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { return(RateLimitPartition.GetConcurrencyLimiter(1, _ => new ConcurrencyLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1))); }); var lease = limiter.Acquire(""); Assert.True(lease.IsAcquired); var cts = new CancellationTokenSource(); var waitTask = limiter.WaitAndAcquireAsync("", 1, cts.Token); Assert.False(waitTask.IsCompleted); cts.Cancel(); await Assert.ThrowsAsync <TaskCanceledException>(async() => await waitTask); }