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.Create(1, key => limiterFactory.GetLimiter(key))); } return(RateLimitPartition.Create(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_DisposeAsyncDisposesAllLimiters() { var limiterFactory = new TrackingRateLimiterFactory <int>(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { if (resource == "1") { return(RateLimitPartition.Create(1, key => limiterFactory.GetLimiter(key))); } return(RateLimitPartition.Create(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.Create(1, key => limiterFactory.GetLimiter(key))); } return(RateLimitPartition.CreateConcurrencyLimiter(2, _ => new ConcurrencyLimiterOptions(1, QueueProcessingOrder.OldestFirst, 2))); }); var lease = await limiter.WaitAsync("2"); var wait = limiter.WaitAsync("2"); Assert.False(wait.IsCompleted); // Different partition, should not be blocked by the wait in the other partition await limiter.WaitAsync("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.WaitAsyncCallCount); }
public void Create_MultiplePartitionsWork() { var limiterFactory = new TrackingRateLimiterFactory <int>(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { if (resource == "1") { return(RateLimitPartition.Create(1, key => limiterFactory.GetLimiter(key))); } else { return(RateLimitPartition.Create(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 async Task IdleLimiterIsCleanedUp() { CustomizableLimiter innerLimiter = null; var factoryCallCount = 0; using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { return(RateLimitPartition.Create(1, _ => { factoryCallCount++; innerLimiter = new CustomizableLimiter(); return innerLimiter; })); }); var timerLoopMethod = Utils.StopTimerAndGetTimerFunc(limiter); 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_GetAvailablePermitsCallsUnderlyingPartitionsLimiter() { var limiterFactory = new TrackingRateLimiterFactory <int>(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { return(RateLimitPartition.Create(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.Create(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.Create(1, key => limiterFactory.GetLimiter(key))); }); await limiter.WaitAsync(""); Assert.Equal(1, limiterFactory.Limiters.Count); Assert.Equal(1, limiterFactory.Limiters[0].Limiter.WaitAsyncCallCount); }
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.Create(1, key => { startedTcs.SetResult(null); // block the factory method Assert.True(tcs.Task.Wait(TimeSpan.FromSeconds(10))); return limiterFactory.GetLimiter(key); })); } return(RateLimitPartition.Create(2, key => limiterFactory.GetLimiter(key))); }); var lease = await limiter.WaitAsync("2"); var blockedTask = Task.Run(async() => { await limiter.WaitAsync("1"); }); await startedTcs.Task; // Other partitions aren't blocked await limiter.WaitAsync("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.WaitAsync("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.WaitAsyncCallCount); Assert.Equal(2, limiterFactory.Limiters[1].Limiter.WaitAsyncCallCount); }
public void Create_DisposeThrowsForFutureMethodCalls() { var limiterFactory = new TrackingRateLimiterFactory <int>(); using var limiter = PartitionedRateLimiter.Create <string, int>(resource => { return(RateLimitPartition.Create(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.Create(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.Create(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_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.Create(1, _ => new TokenBucketRateLimiter(new TokenBucketRateLimiterOptions(1, QueueProcessingOrder.NewestFirst, 1, TimeSpan.FromMilliseconds(100), 1, false)))); }); var lease = limiter.Acquire(""); Assert.True(lease.IsAcquired); lease = await limiter.WaitAsync(""); Assert.True(lease.IsAcquired); }
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_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.Create(1, _ => limiter1)); } return(RateLimitPartition.Create(2, _ => limiter2)); }); limiter.Acquire("1"); limiter.Acquire("2"); var ex = await Assert.ThrowsAsync <AggregateException>(() => limiter.DisposeAsync().AsTask()); Assert.Equal(2, ex.InnerExceptions.Count); }