< 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: 1856_27024993493
Line coverage
0%
Covered lines: 0
Uncovered lines: 22
Coverable lines: 22
Total lines: 219
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
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    [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
 88    internal LocationResolver(
 89        IServerAddressFinder serverAddressFinder,
 90        IServerAddressCache serverAddressCache,
 91        bool background,
 92        TimeSpan refreshThreshold,
 93        TimeSpan ttl,
 94        ILogger logger)
 95    {
 96        _serverAddressFinder = serverAddressFinder;
 97        _serverAddressCache = serverAddressCache;
 98        _background = background;
 99        _refreshThreshold = refreshThreshold;
 100        _ttl = ttl;
 101        _logger = logger;
 102    }
 103
 104    public ValueTask<(ServiceAddress? ServiceAddress, bool FromCache)> ResolveAsync(
 105        Location location,
 106        bool refreshCache,
 107        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)
 113    {
 114        ServiceAddress? serviceAddress = null;
 115        bool expired = false;
 116        bool justRefreshed = false;
 117        bool resolved = false;
 118
 119        if (_serverAddressCache.TryGetValue(location, out (TimeSpan InsertionTime, ServiceAddress ServiceAddress) entry)
 120        {
 121            serviceAddress = entry.ServiceAddress;
 122            TimeSpan cacheEntryAge = TimeSpan.FromMilliseconds(Environment.TickCount64) - entry.InsertionTime;
 123            expired = _ttl != Timeout.InfiniteTimeSpan && cacheEntryAge > _ttl;
 124            justRefreshed = cacheEntryAge <= _refreshThreshold;
 125        }
 126
 127        if (serviceAddress is null || (!_background && expired) || (refreshCache && !justRefreshed))
 128        {
 129            serviceAddress = await _serverAddressFinder.FindAsync(location, cancellationToken).ConfigureAwait(false);
 130            resolved = true;
 131        }
 132        else if (_background && expired)
 133        {
 134            // We retrieved an expired service address from the cache, so we launch a refresh in the background.
 135            _ = RefreshInBackgroundAsync();
 136        }
 137
 138        // A well-known service address resolution can return a service address with an adapter-id.
 139        if (serviceAddress is not null && serviceAddress.Params.TryGetValue("adapter-id", out string? escapedAdapterId))
 140        {
 141            try
 142            {
 143                // Resolves adapter ID recursively, by checking first the cache. If we resolved the well-known
 144                // service address, we request a cache refresh for the adapter ID.
 145                (serviceAddress, _) = await PerformResolveAsync(
 146                    new Location { IsAdapterId = true, Value = Uri.UnescapeDataString(escapedAdapterId) },
 147                    refreshCache || resolved,
 148                    cancellationToken).ConfigureAwait(false);
 149            }
 150            catch
 151            {
 152                serviceAddress = null;
 153                throw;
 154            }
 155            finally
 156            {
 157                // When the second resolution fails, we clear the cache entry for the initial successful
 158                // resolution, since the overall resolution is a failure.
 159                if (serviceAddress is null)
 160                {
 161                    _serverAddressCache.Remove(location);
 162                }
 163            }
 164        }
 165
 166        return (serviceAddress, serviceAddress is not null && !resolved);
 167
 168        async Task RefreshInBackgroundAsync()
 169        {
 170            try
 171            {
 172                _ = await _serverAddressFinder.FindAsync(location, cancellationToken: default).ConfigureAwait(false);
 173            }
 174            catch (Exception exception)
 175            {
 176                _logger.LogBackgroundRefreshFailed(location.Kind, location, exception);
 177            }
 178        }
 179    }
 180}
 181
 182/// <summary>A decorator that adds event source logging to a location resolver.</summary>
 183internal class LogLocationResolverDecorator : ILocationResolver
 184{
 185    private readonly ILocationResolver _decoratee;
 186    private readonly ILogger _logger;
 187
 188    public async ValueTask<(ServiceAddress? ServiceAddress, bool FromCache)> ResolveAsync(
 189        Location location,
 190        bool refreshCache,
 191        CancellationToken cancellationToken)
 0192    {
 193        try
 0194        {
 0195            (ServiceAddress? serviceAddress, bool fromCache) =
 0196                await _decoratee.ResolveAsync(location, refreshCache, cancellationToken).ConfigureAwait(false);
 0197            if (serviceAddress is not null)
 0198            {
 0199                _logger.LogResolved(location.Kind, location, serviceAddress);
 0200            }
 201            else
 0202            {
 0203                _logger.LogFailedToResolve(location.Kind, location);
 0204            }
 0205            return (serviceAddress, fromCache);
 206        }
 0207        catch (Exception exception)
 0208        {
 0209            _logger.LogFailedToResolve(location.Kind, location, exception);
 0210            throw;
 211        }
 0212    }
 213
 0214    internal LogLocationResolverDecorator(ILocationResolver decoratee, ILogger logger)
 0215    {
 0216        _decoratee = decoratee;
 0217        _logger = logger;
 0218    }
 219}