< Summary

Information
Class: IceRpc.Locator.Internal.LogServerAddressFinderDecorator
Assembly: IceRpc.Locator
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Locator/Internal/ServerAddressFinder.cs
Tag: 1856_27024993493
Line coverage
0%
Covered lines: 0
Uncovered lines: 16
Coverable lines: 16
Total lines: 234
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 2
Branch coverage: 0%
Method coverage
0%
Covered methods: 0
Fully covered methods: 0
Total methods: 2
Method coverage: 0%
Full method coverage: 0%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
FindAsync()0%620%
.ctor(...)100%210%

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.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                IceObjectProxy? proxy = await _locator.FindAdapterByIdAsync(
 56                    location.Value,
 57                    cancellationToken: cancellationToken).ConfigureAwait(false);
 58
 59                if (proxy?.ServiceAddress is ServiceAddress serviceAddress)
 60                {
 61                    return serviceAddress.Protocol == Protocol.Ice && serviceAddress.ServerAddress is not null ?
 62                        serviceAddress :
 63                        throw new InvalidDataException(
 64                            $"The locator returned invalid proxy '{proxy}' when looking up an adapter by ID.");
 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                IceObjectProxy? proxy = await _locator.FindObjectByIdAsync(
 82                    Identity.Parse(location.Value),
 83                    cancellationToken: cancellationToken).ConfigureAwait(false);
 84
 85                if (proxy?.ServiceAddress is ServiceAddress serviceAddress)
 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 proxy '{proxy}' when looking up an object by ID.");
 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)
 0117    {
 118        // We don't log any exceptions here because we expect another decorator further up in chain to log these
 119        // exceptions.
 0120        ServiceAddress? serviceAddress = await _decoratee.FindAsync(location, cancellationToken).ConfigureAwait(false);
 0121        if (serviceAddress is not null)
 0122        {
 0123            _logger.LogFound(location.Kind, location, serviceAddress);
 0124        }
 125        else
 0126        {
 0127            _logger.LogFindFailed(location.Kind, location);
 0128        }
 0129        return serviceAddress;
 0130    }
 131
 0132    internal LogServerAddressFinderDecorator(IServerAddressFinder decoratee, ILogger logger)
 0133    {
 0134        _decoratee = decoratee;
 0135        _logger = logger;
 0136    }
 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;
 175    private readonly Lock _mutex = new();
 176    private readonly Dictionary<Location, Task<ServiceAddress?>> _requests = new();
 177    private readonly TimeSpan _resolveTimeout;
 178
 179    public Task<ServiceAddress?> FindAsync(Location location, CancellationToken cancellationToken)
 180    {
 181        Task<ServiceAddress?>? task;
 182
 183        lock (_mutex)
 184        {
 185            if (!_requests.TryGetValue(location, out task))
 186            {
 187                // If there is no request in progress, we invoke one and cache the request to prevent concurrent
 188                // identical requests. It's removed once the response is received.
 189                task = PerformFindAsync();
 190
 191                if (!task.IsCompleted)
 192                {
 193                    // If PerformFindAsync completed, don't add the task (it would leak since PerformFindAsync
 194                    // is responsible for removing it).
 195                    // Since PerformFindAsync locks _mutex in its finally block, the only way it can
 196                    // be completed now is if completed synchronously.
 197                    _requests.Add(location, task);
 198                }
 199            }
 200        }
 201
 202        return task.WaitAsync(cancellationToken);
 203
 204        // The shared task uses an internal CTS bounded by _resolveTimeout — never the caller's token. Otherwise
 205        // the first caller's cancellation would fault the shared task and propagate to every joined waiter.
 206        // Per-caller cancellation is handled by task.WaitAsync above.
 207        async Task<ServiceAddress?> PerformFindAsync()
 208        {
 209            using var cts = new CancellationTokenSource(_resolveTimeout);
 210            try
 211            {
 212                return await _decoratee.FindAsync(location, cts.Token).ConfigureAwait(false);
 213            }
 214            catch (OperationCanceledException) when (cts.IsCancellationRequested)
 215            {
 216                throw new TimeoutException(
 217                    $"The locator lookup timed out after {_resolveTimeout.TotalSeconds} s.");
 218            }
 219            finally
 220            {
 221                lock (_mutex)
 222                {
 223                    _requests.Remove(location);
 224                }
 225            }
 226        }
 227    }
 228
 229    internal CoalesceServerAddressFinderDecorator(IServerAddressFinder decoratee, TimeSpan resolveTimeout)
 230    {
 231        _decoratee = decoratee;
 232        _resolveTimeout = resolveTimeout;
 233    }
 234}