File: QueuePolicies\BasePolicy.cs
Web Access
Project: src\src\Middleware\ConcurrencyLimiter\src\Microsoft.AspNetCore.ConcurrencyLimiter.csproj (Microsoft.AspNetCore.ConcurrencyLimiter)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Collections.Concurrent;
using System.Threading.RateLimiting;
using Microsoft.Extensions.Options;
using Limiter = System.Threading.RateLimiting.ConcurrencyLimiter;
using LimiterOptions = System.Threading.RateLimiting.ConcurrencyLimiterOptions;
 
namespace Microsoft.AspNetCore.ConcurrencyLimiter;
 
internal class BasePolicy : IQueuePolicy, IDisposable
{
    private readonly Limiter _limiter;
    private readonly ConcurrentQueue<RateLimitLease> _leases = new ConcurrentQueue<RateLimitLease>();
 
    public int TotalRequests => _leases.Count;
 
    public BasePolicy(IOptions<QueuePolicyOptions> options, QueueProcessingOrder order)
    {
        var queuePolicyOptions = options.Value;
 
        var maxConcurrentRequests = queuePolicyOptions.MaxConcurrentRequests;
        if (maxConcurrentRequests <= 0)
        {
            throw new ArgumentException("MaxConcurrentRequests must be a positive integer.", nameof(options));
        }
 
        var requestQueueLimit = queuePolicyOptions.RequestQueueLimit;
        if (requestQueueLimit < 0)
        {
            throw new ArgumentException("The RequestQueueLimit cannot be a negative number.", nameof(options));
        }
 
        _limiter = new Limiter(new LimiterOptions
        {
            PermitLimit = maxConcurrentRequests,
            QueueProcessingOrder = order,
            QueueLimit = requestQueueLimit
        });
    }
 
    public ValueTask<bool> TryEnterAsync()
    {
        // a return value of 'false' indicates that the request is rejected
        // a return value of 'true' indicates that the request may proceed
 
        var lease = _limiter.AttemptAcquire();
        if (lease.IsAcquired)
        {
            _leases.Enqueue(lease);
            return ValueTask.FromResult(true);
        }
 
        var task = _limiter.AcquireAsync();
        if (task.IsCompletedSuccessfully)
        {
            lease = task.Result;
            if (lease.IsAcquired)
            {
                _leases.Enqueue(lease);
                return ValueTask.FromResult(true);
            }
 
            return ValueTask.FromResult(false);
        }
 
        return Awaited(task);
    }
 
    public void OnExit()
    {
        if (!_leases.TryDequeue(out var lease))
        {
            throw new InvalidOperationException("No outstanding leases.");
        }
 
        lease.Dispose();
    }
 
    public void Dispose()
    {
        _limiter.Dispose();
    }
 
    private async ValueTask<bool> Awaited(ValueTask<RateLimitLease> task)
    {
        var lease = await task;
 
        if (lease.IsAcquired)
        {
            _leases.Enqueue(lease);
            return true;
        }
 
        return false;
    }
}