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 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 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 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 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); }
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_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 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 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()); }
public async Task Create_PartitionIsCached() { var limiterFactory = new TrackingRateLimiterFactory <int>(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { return(RateLimitPartition.Get(1, key => limiterFactory.GetLimiter(key))); }); limiter.AttemptAcquire(""); await limiter.AcquireAsync(""); limiter.AttemptAcquire(""); await limiter.AcquireAsync(""); Assert.Equal(1, limiterFactory.Limiters.Count); Assert.Equal(2, limiterFactory.Limiters[0].Limiter.AcquireCallCount); Assert.Equal(2, limiterFactory.Limiters[0].Limiter.AcquireAsyncCallCount); }
public void Create_DisposeWithThrowingDisposes_DisposesAllLimiters() { var limiter1 = new CustomizableLimiter(); limiter1.DisposeImpl = _ => throw new Exception(); var limiter2 = new CustomizableLimiter(); limiter2.DisposeImpl = _ => throw new Exception(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { if (resource == "1") { return(RateLimitPartition.Get(1, _ => limiter1)); } return(RateLimitPartition.Get(2, _ => limiter2)); }); limiter.AttemptAcquire("1"); limiter.AttemptAcquire("2"); var ex = Assert.Throws <AggregateException>(() => limiter.Dispose()); Assert.Equal(2, ex.InnerExceptions.Count); }
public async Task EndpointLimiter_Rejects() { var onRejectedInvoked = false; var options = CreateOptionsAccessor(); var name = "myEndpoint"; options.Value.AddPolicy <string>(name, (context => { return(RateLimitPartition.Get <string>("myLimiter", (key => { return new TestRateLimiter(false); }))); })); options.Value.OnRejected = (context, token) => { onRejectedInvoked = true; context.HttpContext.Response.StatusCode = 429; return(ValueTask.CompletedTask); }; var middleware = new RateLimitingMiddleware(c => { return(Task.CompletedTask); }, new NullLoggerFactory().CreateLogger <RateLimitingMiddleware>(), options, Mock.Of <IServiceProvider>()); var context = new DefaultHttpContext(); context.SetEndpoint(new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(new EnableRateLimitingAttribute(name)), "Test endpoint")); await middleware.Invoke(context).DefaultTimeout(); Assert.True(onRejectedInvoked); Assert.Equal(StatusCodes.Status429TooManyRequests, context.Response.StatusCode); }
public async Task Create_DisposeAsyncWithThrowingDisposes_DisposesAllLimiters() { var limiter1 = new CustomizableLimiter(); limiter1.DisposeAsyncCoreImpl = () => throw new Exception(); var limiter2 = new CustomizableLimiter(); limiter2.DisposeAsyncCoreImpl = () => throw new Exception(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { if (resource == "1") { return(RateLimitPartition.Get(1, _ => limiter1)); } return(RateLimitPartition.Get(2, _ => limiter2)); }); limiter.Acquire("1"); limiter.Acquire("2"); var ex = await Assert.ThrowsAsync <AggregateException>(() => limiter.DisposeAsync().AsTask()); Assert.Equal(2, ex.InnerExceptions.Count); }