File: DefaultCombinedLease.cs
Web Access
Project: src\src\Middleware\RateLimiting\src\Microsoft.AspNetCore.RateLimiting.csproj (Microsoft.AspNetCore.RateLimiting)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
 
using System.Threading.RateLimiting;
 
namespace Microsoft.AspNetCore.RateLimiting;
 
internal sealed class DefaultCombinedLease : RateLimitLease
{
    private readonly RateLimitLease _globalLease;
    private readonly RateLimitLease _endpointLease;
    private HashSet<string>? _metadataNames;
 
    public DefaultCombinedLease(RateLimitLease globalLease, RateLimitLease endpointLease)
    {
        _globalLease = globalLease;
        _endpointLease = endpointLease;
    }
 
    public override bool IsAcquired => true;
 
    public override IEnumerable<string> MetadataNames
    {
        get
        {
            if (_metadataNames is null)
            {
                _metadataNames = new HashSet<string>();
                foreach (var metadataName in _globalLease.MetadataNames)
                {
                    _metadataNames.Add(metadataName);
                }
                foreach (var metadataName in _endpointLease.MetadataNames)
                {
                    _metadataNames.Add(metadataName);
                }
            }
            return _metadataNames;
        }
    }
 
    public override bool TryGetMetadata(string metadataName, out object? metadata)
    {
        // Use the first metadata item of a given name, ignore duplicates, we can't reliably merge arbitrary metadata
        // Creating an object[] if there are multiple of the same metadataName could work, but makes consumption of metadata messy
        // and makes MetadataName.Create<T>(...) uses no longer work
        if (_endpointLease.TryGetMetadata(metadataName, out metadata))
        {
            return true;
        }
        if (_globalLease.TryGetMetadata(metadataName, out metadata))
        {
            return true;
        }
 
        metadata = null;
        return false;
    }
 
    protected override void Dispose(bool disposing)
    {
        List<Exception>? exceptions = null;
 
        // Dispose endpoint lease first, then global lease (reverse order of when they were acquired)
        // Avoids issues where dispose might unblock a queued acquire and then the acquire fails when acquiring the next limiter.
        // When disposing in reverse order there wont be any issues of unblocking an acquire that affects acquires on limiters in the chain after it
        try
        {
            _endpointLease.Dispose();
        }
        catch (Exception ex)
        {
            exceptions ??= new List<Exception>();
            exceptions.Add(ex);
        }
 
        try
        {
            _globalLease.Dispose();
        }
        catch (Exception ex)
        {
            exceptions ??= new List<Exception>(1);
            exceptions.Add(ex);
        }
 
        if (exceptions is not null)
        {
            if (exceptions.Count == 1)
            {
                throw exceptions[0];
            }
            else
            {
                throw new AggregateException(exceptions);
            }
        }
    }
}