< Summary

Information
Class: IceRpc.Locator.Internal.CoalesceServerAddressFinderDecorator
Assembly: IceRpc.Locator
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Locator/Internal/ServerAddressFinder.cs
Tag: 275_13775359185
Line coverage
100%
Covered lines: 28
Uncovered lines: 0
Coverable lines: 28
Total lines: 221
Line coverage: 100%
Branch coverage
100%
Covered branches: 4
Total branches: 4
Branch coverage: 100%
Method coverage
100%
Covered methods: 3
Total methods: 3
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
FindAsync(...)100%44100%
PerformFindAsync()100%11100%

File(s)

/home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Locator/Internal/ServerAddressFinder.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using IceRpc.Slice.Ice;
 4using Microsoft.Extensions.Logging;
 5
 6namespace IceRpc.Locator.Internal;
 7
 8/// <summary>Provides extension methods for <see cref="ILogger" />. They are used by <see
 9/// cref="LogServerAddressFinderDecorator"/>.</summary>
 10internal static partial class ServerAddressFinderLoggerExtensions
 11{
 12    [LoggerMessage(
 13        EventId = (int)LocationEventId.FindFailed,
 14        EventName = nameof(LocationEventId.FindFailed),
 15        Level = LogLevel.Trace,
 16        Message = "Failed to find {LocationKind} '{Location}'")]
 17    internal static partial void LogFindFailed(
 18        this ILogger logger,
 19        string locationKind,
 20        Location location);
 21
 22    [LoggerMessage(
 23        EventId = (int)LocationEventId.Found,
 24        EventName = nameof(LocationEventId.Found),
 25        Level = LogLevel.Trace,
 26        Message = "Found {LocationKind} '{Location}' = '{ServiceAddress}'")]
 27    internal static partial void LogFound(
 28        this ILogger logger,
 29        string locationKind,
 30        Location location,
 31        ServiceAddress serviceAddress);
 32}
 33
 34/// <summary>A server address finder finds the server address(es) of a location. These server address(es) are carried by
 35/// a dummy service address. When this dummy service address is not null, its ServerAddress property is guaranteed to be
 36/// not <see langword="null" />. Unlike <see cref="ILocationResolver" />, a server address finder does not provide
 37/// cache-related parameters and typically does not maintain a cache.</summary>
 38internal interface IServerAddressFinder
 39{
 40    Task<ServiceAddress?> FindAsync(Location location, CancellationToken cancellationToken);
 41}
 42
 43/// <summary>The main implementation of IServerAddressFinder. It uses an <see cref="ILocator"/> to "find" the server
 44/// addresses.</summary>
 45internal class LocatorServerAddressFinder : IServerAddressFinder
 46{
 47    private readonly ILocator _locator;
 48
 49    public async Task<ServiceAddress?> FindAsync(Location location, CancellationToken cancellationToken)
 50    {
 51        if (location.IsAdapterId)
 52        {
 53            try
 54            {
 55                ServiceAddress? serviceAddress = await _locator.FindAdapterByIdAsync(
 56                    location.Value,
 57                    cancellationToken: cancellationToken).ConfigureAwait(false);
 58
 59                if (serviceAddress is not null)
 60                {
 61                    return serviceAddress.Protocol == Protocol.Ice && serviceAddress.ServerAddress is not null ?
 62                        serviceAddress :
 63                        throw new InvalidDataException(
 64                            $"The locator returned invalid service address '{serviceAddress}' when looking up an adapter
 65                }
 66                else
 67                {
 68                    return null;
 69                }
 70            }
 71            catch (AdapterNotFoundException)
 72            {
 73                // We treat AdapterNotFoundException just like a null return value.
 74                return null;
 75            }
 76        }
 77        else
 78        {
 79            try
 80            {
 81                ServiceAddress? serviceAddress = await _locator.FindObjectByIdAsync(
 82                    location.Value,
 83                    cancellationToken: cancellationToken).ConfigureAwait(false);
 84
 85                if (serviceAddress is not null)
 86                {
 87                    // findObjectById can return an indirect service address with an adapter ID
 88                    return serviceAddress.Protocol == Protocol.Ice &&
 89                        (serviceAddress.ServerAddress is not null || serviceAddress.Params.ContainsKey("adapter-id")) ?
 90                            serviceAddress :
 91                            throw new InvalidDataException(
 92                                $"The locator returned invalid service address '{serviceAddress}' when looking up an obj
 93                }
 94                else
 95                {
 96                    return null;
 97                }
 98            }
 99            catch (ObjectNotFoundException)
 100            {
 101                // We treat ObjectNotFoundException just like a null return value.
 102                return null;
 103            }
 104        }
 105    }
 106
 107    internal LocatorServerAddressFinder(ILocator locator) => _locator = locator;
 108}
 109
 110/// <summary>A decorator that adds logging to a server address finder.</summary>
 111internal class LogServerAddressFinderDecorator : IServerAddressFinder
 112{
 113    private readonly IServerAddressFinder _decoratee;
 114    private readonly ILogger _logger;
 115
 116    public async Task<ServiceAddress?> FindAsync(Location location, CancellationToken cancellationToken)
 117    {
 118        // We don't log any exceptions here because we expect another decorator further up in chain to log these
 119        // exceptions.
 120        ServiceAddress? serviceAddress = await _decoratee.FindAsync(location, cancellationToken).ConfigureAwait(false);
 121        if (serviceAddress is not null)
 122        {
 123            _logger.LogFound(location.Kind, location, serviceAddress);
 124        }
 125        else
 126        {
 127            _logger.LogFindFailed(location.Kind, location);
 128        }
 129        return serviceAddress;
 130    }
 131
 132    internal LogServerAddressFinderDecorator(IServerAddressFinder decoratee, ILogger logger)
 133    {
 134        _decoratee = decoratee;
 135        _logger = logger;
 136    }
 137}
 138
 139/// <summary>A decorator that updates its server address cache after a call to its decoratee (e.g. remote locator). It
 140/// needs to execute downstream from the Coalesce decorator.</summary>
 141internal class CacheUpdateServerAddressFinderDecorator : IServerAddressFinder
 142{
 143    private readonly IServerAddressFinder _decoratee;
 144    private readonly IServerAddressCache _serverAddressCache;
 145
 146    public async Task<ServiceAddress?> FindAsync(Location location, CancellationToken cancellationToken)
 147    {
 148        ServiceAddress? serviceAddress = await _decoratee.FindAsync(location, cancellationToken).ConfigureAwait(false);
 149
 150        if (serviceAddress is not null)
 151        {
 152            _serverAddressCache.Set(location, serviceAddress);
 153        }
 154        else
 155        {
 156            _serverAddressCache.Remove(location);
 157        }
 158        return serviceAddress;
 159    }
 160
 161    internal CacheUpdateServerAddressFinderDecorator(
 162        IServerAddressFinder decoratee,
 163        IServerAddressCache serverAddressCache)
 164    {
 165        _serverAddressCache = serverAddressCache;
 166        _decoratee = decoratee;
 167    }
 168}
 169
 170/// <summary>A decorator that detects multiple concurrent identical FindAsync and "coalesce" them to avoid overloading
 171/// the decoratee (e.g. the remote locator).</summary>
 172internal class CoalesceServerAddressFinderDecorator : IServerAddressFinder
 173{
 174    private readonly IServerAddressFinder _decoratee;
 3175    private readonly object _mutex = new();
 3176    private readonly Dictionary<Location, Task<ServiceAddress?>> _requests = new();
 177
 178    public Task<ServiceAddress?> FindAsync(Location location, CancellationToken cancellationToken)
 6179    {
 180        Task<ServiceAddress?>? task;
 181
 6182        lock (_mutex)
 6183        {
 6184            if (!_requests.TryGetValue(location, out task))
 4185            {
 186                // If there is no request in progress, we invoke one and cache the request to prevent concurrent
 187                // identical requests. It's removed once the response is received.
 4188                task = PerformFindAsync();
 189
 4190                if (!task.IsCompleted)
 1191                {
 192                    // If PerformFindAsync completed, don't add the task (it would leak since PerformFindAsync
 193                    // is responsible for removing it).
 194                    // Since PerformFindAsync locks _mutex in its finally block, the only way it can
 195                    // be completed now is if completed synchronously.
 1196                    _requests.Add(location, task);
 1197                }
 4198            }
 6199        }
 200
 6201        return task.WaitAsync(cancellationToken);
 202
 203        async Task<ServiceAddress?> PerformFindAsync()
 4204        {
 205            try
 4206            {
 4207                return await _decoratee.FindAsync(location, cancellationToken).ConfigureAwait(false);
 208            }
 209            finally
 4210            {
 4211                lock (_mutex)
 4212                {
 4213                    _requests.Remove(location);
 4214                }
 4215            }
 4216        }
 6217    }
 218
 3219    internal CoalesceServerAddressFinderDecorator(IServerAddressFinder decoratee) =>
 3220        _decoratee = decoratee;
 221}