< Summary

Information
Class: IceRpc.Locator.Internal.LocationResolver
Assembly: IceRpc.Locator
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Locator/Internal/LocationResolver.cs
Tag: 1986_28452893481
Line coverage
88%
Covered lines: 60
Uncovered lines: 8
Coverable lines: 68
Total lines: 223
Line coverage: 88.2%
Branch coverage
100%
Covered branches: 22
Total branches: 22
Branch coverage: 100%
Method coverage
100%
Covered methods: 4
Fully covered methods: 2
Total methods: 4
Method coverage: 100%
Full method coverage: 50%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
ResolveAsync(...)100%11100%
PerformResolveAsync()100%222290.69%
RefreshInBackgroundAsync()100%1155.55%

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    [LoggerMessage(
 34        EventId = (int)LocationEventId.BackgroundRefreshFailed,
 35        EventName = nameof(LocationEventId.BackgroundRefreshFailed),
 36        Level = LogLevel.Debug,
 37        Message = "Background cache refresh failed for {LocationKind} '{Location}'")]
 38    internal static partial void LogBackgroundRefreshFailed(
 39        this ILogger logger,
 40        string locationKind,
 41        Location location,
 42        Exception exception);
 43}
 44
 45/// <summary>An implementation of <see cref="ILocationResolver" /> without a cache.</summary>
 46internal class CacheLessLocationResolver : ILocationResolver
 47{
 48    private readonly IServerAddressFinder _serverAddressFinder;
 49
 50    internal CacheLessLocationResolver(IServerAddressFinder serverAddressFinder) =>
 51        _serverAddressFinder = serverAddressFinder;
 52
 53    public ValueTask<(ServiceAddress? ServiceAddress, bool FromCache)> ResolveAsync(
 54        Location location,
 55        bool refreshCache,
 56        CancellationToken cancellationToken) => ResolveAsync(location, cancellationToken);
 57
 58    private async ValueTask<(ServiceAddress? ServiceAddress, bool FromCache)> ResolveAsync(
 59        Location location,
 60        CancellationToken cancellationToken)
 61    {
 62        ServiceAddress? serviceAddress = await _serverAddressFinder.FindAsync(location, cancellationToken)
 63            .ConfigureAwait(false);
 64
 65        // A well-known service address resolution can return a service address with an adapter ID
 66        if (serviceAddress is not null && serviceAddress.Params.TryGetValue("adapter-id", out string? escapedAdapterId))
 67        {
 68            (serviceAddress, _) = await ResolveAsync(
 69                new Location { IsAdapterId = true, Value = Uri.UnescapeDataString(escapedAdapterId) },
 70                cancellationToken).ConfigureAwait(false);
 71        }
 72
 73        return (serviceAddress, false);
 74    }
 75}
 76
 77/// <summary>The main implementation of <see cref="ILocationResolver" />, with a cache.</summary>
 78internal class LocationResolver : ILocationResolver
 79{
 80    private readonly bool _background;
 81    private readonly ILogger _logger;
 82    private readonly IServerAddressCache _serverAddressCache;
 83    private readonly IServerAddressFinder _serverAddressFinder;
 84    private readonly TimeSpan _refreshThreshold;
 85
 86    private readonly TimeSpan _ttl;
 87
 988    internal LocationResolver(
 989        IServerAddressFinder serverAddressFinder,
 990        IServerAddressCache serverAddressCache,
 991        bool background,
 992        TimeSpan refreshThreshold,
 993        TimeSpan ttl,
 994        ILogger logger)
 995    {
 996        _serverAddressFinder = serverAddressFinder;
 997        _serverAddressCache = serverAddressCache;
 998        _background = background;
 999        _refreshThreshold = refreshThreshold;
 9100        _ttl = ttl;
 9101        _logger = logger;
 9102    }
 103
 104    public ValueTask<(ServiceAddress? ServiceAddress, bool FromCache)> ResolveAsync(
 105        Location location,
 106        bool refreshCache,
 10107        CancellationToken cancellationToken) => PerformResolveAsync(location, refreshCache, cancellationToken);
 108
 109    private async ValueTask<(ServiceAddress? ServiceAddress, bool FromCache)> PerformResolveAsync(
 110        Location location,
 111        bool refreshCache,
 112        CancellationToken cancellationToken)
 13113    {
 13114        ServiceAddress? serviceAddress = null;
 13115        bool expired = false;
 13116        bool justRefreshed = false;
 13117        bool resolved = false;
 118
 13119        if (_serverAddressCache.TryGetValue(location, out (TimeSpan InsertionTime, ServiceAddress ServiceAddress) entry)
 8120        {
 8121            serviceAddress = entry.ServiceAddress;
 8122            TimeSpan cacheEntryAge = TimeSpan.FromMilliseconds(Environment.TickCount64) - entry.InsertionTime;
 8123            expired = _ttl != Timeout.InfiniteTimeSpan && cacheEntryAge > _ttl;
 8124            justRefreshed = cacheEntryAge <= _refreshThreshold;
 8125        }
 126
 13127        if (serviceAddress is null || (!_background && expired) || (refreshCache && !justRefreshed))
 7128        {
 7129            serviceAddress = await _serverAddressFinder.FindAsync(location, cancellationToken).ConfigureAwait(false);
 7130            resolved = true;
 7131        }
 6132        else if (_background && expired)
 1133        {
 134            // We retrieved an expired service address from the cache, so we launch a refresh in the background.
 1135            _ = RefreshInBackgroundAsync();
 1136        }
 137
 13138        bool adapterIdFromCache = false;
 139
 140        // A well-known service address resolution can return a service address with an adapter-id.
 13141        if (serviceAddress is not null && serviceAddress.Params.TryGetValue("adapter-id", out string? escapedAdapterId))
 3142        {
 143            try
 3144            {
 145                // Resolves adapter ID recursively, by checking first the cache. If we resolved the well-known
 146                // service address, we request a cache refresh for the adapter ID.
 3147                (serviceAddress, adapterIdFromCache) = await PerformResolveAsync(
 3148                    new Location { IsAdapterId = true, Value = Uri.UnescapeDataString(escapedAdapterId) },
 3149                    refreshCache || resolved,
 3150                    cancellationToken).ConfigureAwait(false);
 3151            }
 0152            catch
 0153            {
 0154                serviceAddress = null;
 0155                throw;
 156            }
 157            finally
 3158            {
 159                // When the second resolution fails, we clear the cache entry for the initial successful
 160                // resolution, since the overall resolution is a failure.
 3161                if (serviceAddress is null)
 1162                {
 1163                    _serverAddressCache.Remove(location);
 1164                }
 3165            }
 3166        }
 167
 168        // The resolution is from the cache if this location's lookup was served from the cache, or if the recursive
 169        // adapter-id resolution was served from the cache.
 13170        return (serviceAddress, serviceAddress is not null && (!resolved || adapterIdFromCache));
 171
 172        async Task RefreshInBackgroundAsync()
 1173        {
 174            try
 1175            {
 1176                _ = await _serverAddressFinder.FindAsync(location, cancellationToken: default).ConfigureAwait(false);
 1177            }
 0178            catch (Exception exception)
 0179            {
 0180                _logger.LogBackgroundRefreshFailed(location.Kind, location, exception);
 0181            }
 1182        }
 13183    }
 184}
 185
 186/// <summary>A decorator that adds event source logging to a location resolver.</summary>
 187internal class LogLocationResolverDecorator : ILocationResolver
 188{
 189    private readonly ILocationResolver _decoratee;
 190    private readonly ILogger _logger;
 191
 192    public async ValueTask<(ServiceAddress? ServiceAddress, bool FromCache)> ResolveAsync(
 193        Location location,
 194        bool refreshCache,
 195        CancellationToken cancellationToken)
 196    {
 197        try
 198        {
 199            (ServiceAddress? serviceAddress, bool fromCache) =
 200                await _decoratee.ResolveAsync(location, refreshCache, cancellationToken).ConfigureAwait(false);
 201            if (serviceAddress is not null)
 202            {
 203                _logger.LogResolved(location.Kind, location, serviceAddress);
 204            }
 205            else
 206            {
 207                _logger.LogFailedToResolve(location.Kind, location);
 208            }
 209            return (serviceAddress, fromCache);
 210        }
 211        catch (Exception exception)
 212        {
 213            _logger.LogFailedToResolve(location.Kind, location, exception);
 214            throw;
 215        }
 216    }
 217
 218    internal LogLocationResolverDecorator(ILocationResolver decoratee, ILogger logger)
 219    {
 220        _decoratee = decoratee;
 221        _logger = logger;
 222    }
 223}