| | 1 | | // Copyright (c) ZeroC, Inc. |
| | 2 | |
|
| | 3 | | using Microsoft.Extensions.Logging; |
| | 4 | |
|
| | 5 | | namespace IceRpc.Locator.Internal; |
| | 6 | |
|
| | 7 | | /// <summary>Provides extension methods for <see cref="ILogger" />. They are used by <see |
| | 8 | | /// cref="LogLocationResolverDecorator"/>.</summary> |
| | 9 | | internal 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> |
| | 35 | | internal 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> |
| | 67 | | internal 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 | |
|
| 8 | 76 | | internal LocationResolver( |
| 8 | 77 | | IServerAddressFinder serverAddressFinder, |
| 8 | 78 | | IServerAddressCache serverAddressCache, |
| 8 | 79 | | bool background, |
| 8 | 80 | | TimeSpan refreshThreshold, |
| 8 | 81 | | TimeSpan ttl) |
| 8 | 82 | | { |
| 8 | 83 | | _serverAddressFinder = serverAddressFinder; |
| 8 | 84 | | _serverAddressCache = serverAddressCache; |
| 8 | 85 | | _background = background; |
| 8 | 86 | | _refreshThreshold = refreshThreshold; |
| 8 | 87 | | _ttl = ttl; |
| 8 | 88 | | } |
| | 89 | |
|
| | 90 | | public ValueTask<(ServiceAddress? ServiceAddress, bool FromCache)> ResolveAsync( |
| | 91 | | Location location, |
| | 92 | | bool refreshCache, |
| 9 | 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) |
| 11 | 99 | | { |
| 11 | 100 | | ServiceAddress? serviceAddress = null; |
| 11 | 101 | | bool expired = false; |
| 11 | 102 | | bool justRefreshed = false; |
| 11 | 103 | | bool resolved = false; |
| | 104 | |
|
| 11 | 105 | | if (_serverAddressCache.TryGetValue(location, out (TimeSpan InsertionTime, ServiceAddress ServiceAddress) entry) |
| 7 | 106 | | { |
| 7 | 107 | | serviceAddress = entry.ServiceAddress; |
| 7 | 108 | | TimeSpan cacheEntryAge = TimeSpan.FromMilliseconds(Environment.TickCount64) - entry.InsertionTime; |
| 7 | 109 | | expired = _ttl != Timeout.InfiniteTimeSpan && cacheEntryAge > _ttl; |
| 7 | 110 | | justRefreshed = cacheEntryAge <= _refreshThreshold; |
| 7 | 111 | | } |
| | 112 | |
|
| 11 | 113 | | if (serviceAddress is null || (!_background && expired) || (refreshCache && !justRefreshed)) |
| 6 | 114 | | { |
| 6 | 115 | | serviceAddress = await _serverAddressFinder.FindAsync(location, cancellationToken).ConfigureAwait(false); |
| 6 | 116 | | resolved = true; |
| 6 | 117 | | } |
| 5 | 118 | | else if (_background && expired) |
| 1 | 119 | | { |
| | 120 | | // We retrieved an expired service address from the cache, so we launch a refresh in the background. |
| 1 | 121 | | _ = _serverAddressFinder.FindAsync(location, cancellationToken: default).ConfigureAwait(false); |
| 1 | 122 | | } |
| | 123 | |
|
| | 124 | | // A well-known service address resolution can return a service address with an adapter-id. |
| 11 | 125 | | if (serviceAddress is not null && serviceAddress.Params.TryGetValue("adapter-id", out string? adapterId)) |
| 2 | 126 | | { |
| | 127 | | try |
| 2 | 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. |
| 2 | 131 | | (serviceAddress, _) = await PerformResolveAsync( |
| 2 | 132 | | new Location { IsAdapterId = true, Value = adapterId }, |
| 2 | 133 | | refreshCache || resolved, |
| 2 | 134 | | cancellationToken).ConfigureAwait(false); |
| 2 | 135 | | } |
| 0 | 136 | | catch |
| 0 | 137 | | { |
| 0 | 138 | | serviceAddress = null; |
| 0 | 139 | | throw; |
| | 140 | | } |
| | 141 | | finally |
| 2 | 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. |
| 2 | 145 | | if (serviceAddress is null) |
| 1 | 146 | | { |
| 1 | 147 | | _serverAddressCache.Remove(location); |
| 1 | 148 | | } |
| 2 | 149 | | } |
| 2 | 150 | | } |
| | 151 | |
|
| 11 | 152 | | return (serviceAddress, serviceAddress is not null && !resolved); |
| 11 | 153 | | } |
| | 154 | | } |
| | 155 | |
|
| | 156 | | /// <summary>A decorator that adds event source logging to a location resolver.</summary> |
| | 157 | | internal 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) |
| | 166 | | { |
| | 167 | | try |
| | 168 | | { |
| | 169 | | (ServiceAddress? serviceAddress, bool fromCache) = |
| | 170 | | await _decoratee.ResolveAsync(location, refreshCache, cancellationToken).ConfigureAwait(false); |
| | 171 | | if (serviceAddress is not null) |
| | 172 | | { |
| | 173 | | _logger.LogResolved(location.Kind, location, serviceAddress); |
| | 174 | | } |
| | 175 | | else |
| | 176 | | { |
| | 177 | | _logger.LogFailedToResolve(location.Kind, location); |
| | 178 | | } |
| | 179 | | return (serviceAddress, fromCache); |
| | 180 | | } |
| | 181 | | catch (Exception exception) |
| | 182 | | { |
| | 183 | | _logger.LogFailedToResolve(location.Kind, location, exception); |
| | 184 | | throw; |
| | 185 | | } |
| | 186 | | } |
| | 187 | |
|
| | 188 | | internal LogLocationResolverDecorator(ILocationResolver decoratee, ILogger logger) |
| | 189 | | { |
| | 190 | | _decoratee = decoratee; |
| | 191 | | _logger = logger; |
| | 192 | | } |
| | 193 | | } |