< Summary

Information
Class: IceRpc.Locator.Internal.LogLocationResolverDecorator
Assembly: IceRpc.Locator
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Locator/Internal/LocationResolver.cs
Tag: 275_13775359185
Line coverage
0%
Covered lines: 0
Uncovered lines: 22
Coverable lines: 22
Total lines: 193
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 2
Branch coverage: 0%
Method coverage
0%
Covered methods: 0
Total methods: 2
Method coverage: 0%

Metrics

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

File(s)

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

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using Microsoft.Extensions.Logging;
 4
 5namespace IceRpc.Locator.Internal;
 6
 7/// <summary>Provides extension methods for <see cref="ILogger" />. They are used by <see
 8/// cref="LogLocationResolverDecorator"/>.</summary>
 9internal static partial class LocatorLoggerExtensions
 10{
 11    [LoggerMessage(
 12        EventId = (int)LocationEventId.Resolved,
 13        EventName = nameof(LocationEventId.Resolved),
 14        Level = LogLevel.Debug,
 15        Message = "Resolved {LocationKind} '{Location}' = '{ServiceAddress}'")]
 16    internal static partial void LogResolved(
 17        this ILogger logger,
 18        string locationKind,
 19        Location location,
 20        ServiceAddress serviceAddress);
 21
 22    [LoggerMessage(
 23        EventId = (int)LocationEventId.FailedToResolve,
 24        EventName = nameof(LocationEventId.FailedToResolve),
 25        Level = LogLevel.Debug,
 26        Message = "Failed to resolve {LocationKind} '{Location}'")]
 27    internal static partial void LogFailedToResolve(
 28        this ILogger logger,
 29        string locationKind,
 30        Location location,
 31        Exception? exception = null);
 32}
 33
 34/// <summary>An implementation of <see cref="ILocationResolver" /> without a cache.</summary>
 35internal class CacheLessLocationResolver : ILocationResolver
 36{
 37    private readonly IServerAddressFinder _serverAddressFinder;
 38
 39    internal CacheLessLocationResolver(IServerAddressFinder serverAddressFinder) =>
 40        _serverAddressFinder = serverAddressFinder;
 41
 42    public ValueTask<(ServiceAddress? ServiceAddress, bool FromCache)> ResolveAsync(
 43        Location location,
 44        bool refreshCache,
 45        CancellationToken cancellationToken) => ResolveAsync(location, cancellationToken);
 46
 47    private async ValueTask<(ServiceAddress? ServiceAddress, bool FromCache)> ResolveAsync(
 48        Location location,
 49        CancellationToken cancellationToken)
 50    {
 51        ServiceAddress? serviceAddress = await _serverAddressFinder.FindAsync(location, cancellationToken)
 52            .ConfigureAwait(false);
 53
 54        // A well-known service address resolution can return a service address with an adapter ID
 55        if (serviceAddress is not null && serviceAddress.Params.TryGetValue("adapter-id", out string? adapterId))
 56        {
 57            (serviceAddress, _) = await ResolveAsync(
 58                new Location { IsAdapterId = true, Value = adapterId },
 59                cancellationToken).ConfigureAwait(false);
 60        }
 61
 62        return (serviceAddress, false);
 63    }
 64}
 65
 66/// <summary>The main implementation of <see cref="ILocationResolver" />, with a cache.</summary>
 67internal class LocationResolver : ILocationResolver
 68{
 69    private readonly bool _background;
 70    private readonly IServerAddressCache _serverAddressCache;
 71    private readonly IServerAddressFinder _serverAddressFinder;
 72    private readonly TimeSpan _refreshThreshold;
 73
 74    private readonly TimeSpan _ttl;
 75
 76    internal LocationResolver(
 77        IServerAddressFinder serverAddressFinder,
 78        IServerAddressCache serverAddressCache,
 79        bool background,
 80        TimeSpan refreshThreshold,
 81        TimeSpan ttl)
 82    {
 83        _serverAddressFinder = serverAddressFinder;
 84        _serverAddressCache = serverAddressCache;
 85        _background = background;
 86        _refreshThreshold = refreshThreshold;
 87        _ttl = ttl;
 88    }
 89
 90    public ValueTask<(ServiceAddress? ServiceAddress, bool FromCache)> ResolveAsync(
 91        Location location,
 92        bool refreshCache,
 93        CancellationToken cancellationToken) => PerformResolveAsync(location, refreshCache, cancellationToken);
 94
 95    private async ValueTask<(ServiceAddress? ServiceAddress, bool FromCache)> PerformResolveAsync(
 96        Location location,
 97        bool refreshCache,
 98        CancellationToken cancellationToken)
 99    {
 100        ServiceAddress? serviceAddress = null;
 101        bool expired = false;
 102        bool justRefreshed = false;
 103        bool resolved = false;
 104
 105        if (_serverAddressCache.TryGetValue(location, out (TimeSpan InsertionTime, ServiceAddress ServiceAddress) entry)
 106        {
 107            serviceAddress = entry.ServiceAddress;
 108            TimeSpan cacheEntryAge = TimeSpan.FromMilliseconds(Environment.TickCount64) - entry.InsertionTime;
 109            expired = _ttl != Timeout.InfiniteTimeSpan && cacheEntryAge > _ttl;
 110            justRefreshed = cacheEntryAge <= _refreshThreshold;
 111        }
 112
 113        if (serviceAddress is null || (!_background && expired) || (refreshCache && !justRefreshed))
 114        {
 115            serviceAddress = await _serverAddressFinder.FindAsync(location, cancellationToken).ConfigureAwait(false);
 116            resolved = true;
 117        }
 118        else if (_background && expired)
 119        {
 120            // We retrieved an expired service address from the cache, so we launch a refresh in the background.
 121            _ = _serverAddressFinder.FindAsync(location, cancellationToken: default).ConfigureAwait(false);
 122        }
 123
 124        // A well-known service address resolution can return a service address with an adapter-id.
 125        if (serviceAddress is not null && serviceAddress.Params.TryGetValue("adapter-id", out string? adapterId))
 126        {
 127            try
 128            {
 129                // Resolves adapter ID recursively, by checking first the cache. If we resolved the well-known
 130                // service address, we request a cache refresh for the adapter ID.
 131                (serviceAddress, _) = await PerformResolveAsync(
 132                    new Location { IsAdapterId = true, Value = adapterId },
 133                    refreshCache || resolved,
 134                    cancellationToken).ConfigureAwait(false);
 135            }
 136            catch
 137            {
 138                serviceAddress = null;
 139                throw;
 140            }
 141            finally
 142            {
 143                // When the second resolution fails, we clear the cache entry for the initial successful
 144                // resolution, since the overall resolution is a failure.
 145                if (serviceAddress is null)
 146                {
 147                    _serverAddressCache.Remove(location);
 148                }
 149            }
 150        }
 151
 152        return (serviceAddress, serviceAddress is not null && !resolved);
 153    }
 154}
 155
 156/// <summary>A decorator that adds event source logging to a location resolver.</summary>
 157internal class LogLocationResolverDecorator : ILocationResolver
 158{
 159    private readonly ILocationResolver _decoratee;
 160    private readonly ILogger _logger;
 161
 162    public async ValueTask<(ServiceAddress? ServiceAddress, bool FromCache)> ResolveAsync(
 163        Location location,
 164        bool refreshCache,
 165        CancellationToken cancellationToken)
 0166    {
 167        try
 0168        {
 0169            (ServiceAddress? serviceAddress, bool fromCache) =
 0170                await _decoratee.ResolveAsync(location, refreshCache, cancellationToken).ConfigureAwait(false);
 0171            if (serviceAddress is not null)
 0172            {
 0173                _logger.LogResolved(location.Kind, location, serviceAddress);
 0174            }
 175            else
 0176            {
 0177                _logger.LogFailedToResolve(location.Kind, location);
 0178            }
 0179            return (serviceAddress, fromCache);
 180        }
 0181        catch (Exception exception)
 0182        {
 0183            _logger.LogFailedToResolve(location.Kind, location, exception);
 0184            throw;
 185        }
 0186    }
 187
 0188    internal LogLocationResolverDecorator(ILocationResolver decoratee, ILogger logger)
 0189    {
 0190        _decoratee = decoratee;
 0191        _logger = logger;
 0192    }
 193}