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 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());
        }
    /// <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 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);
        }