< Summary

Information
Class: IceRpc.Locator.Internal.ServerAddressCache
Assembly: IceRpc.Locator
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Locator/Internal/ServerAddressCache.cs
Tag: 275_13775359185
Line coverage
100%
Covered lines: 44
Uncovered lines: 0
Coverable lines: 44
Total lines: 169
Line coverage: 100%
Branch coverage
100%
Covered branches: 6
Total branches: 6
Branch coverage: 100%
Method coverage
100%
Covered methods: 4
Total methods: 4
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.ctor(...)100%11100%
Remove(...)100%22100%
Set(...)100%22100%
TryGetValue(...)100%22100%

File(s)

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

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using Microsoft.Extensions.Logging;
 4using System.Collections.Concurrent;
 5using System.Diagnostics;
 6
 7namespace IceRpc.Locator.Internal;
 8
 9/// <summary>Provides extension methods for <see cref="ILogger" />. They are used by <see
 10/// cref="LogServerAddressCacheDecorator"/>.
 11/// </summary>
 12internal static partial class ServerAddressCacheLoggerExtensions
 13{
 14    [LoggerMessage(
 15        EventId = (int)LocationEventId.FoundEntry,
 16        EventName = nameof(LocationEventId.FoundEntry),
 17        Level = LogLevel.Trace,
 18        Message = "Found {LocationKind} '{Location}' = '{ServiceAddress}' in cache")]
 19    internal static partial void LogFoundEntry(
 20        this ILogger logger,
 21        string locationKind,
 22        Location location,
 23        ServiceAddress serviceAddress);
 24
 25    [LoggerMessage(
 26        EventId = (int)LocationEventId.SetEntry,
 27        EventName = nameof(LocationEventId.SetEntry),
 28        Level = LogLevel.Trace,
 29        Message = "Set {LocationKind} '{Location}' = '{ServiceAddress}' in cache")]
 30    internal static partial void LogSetEntry(
 31        this ILogger logger,
 32        string locationKind,
 33        Location location,
 34        ServiceAddress serviceAddress);
 35
 36    [LoggerMessage(
 37        EventId = (int)LocationEventId.RemovedEntry,
 38        EventName = nameof(LocationEventId.RemovedEntry),
 39        Level = LogLevel.Trace,
 40        Message = "Removed {LocationKind} '{Location}' from cache")]
 41    internal static partial void LogRemovedEntry(
 42        this ILogger logger,
 43        string locationKind,
 44        Location location);
 45}
 46
 47/// <summary>A server address cache maintains a dictionary of location to server address(es), where the server
 48/// addresses are held by a dummy service address. It also keeps track of the insertion time of each entry. It's
 49/// consumed by <see cref="LocationResolver" />.</summary>
 50internal interface IServerAddressCache
 51{
 52    void Remove(Location location);
 53
 54    void Set(Location location, ServiceAddress serviceAddress);
 55
 56    bool TryGetValue(Location location, out (TimeSpan InsertionTime, ServiceAddress ServiceAddress) value);
 57}
 58
 59/// <summary>The main implementation for <see cref="IServerAddressCache" />.</summary>
 60internal sealed class ServerAddressCache : IServerAddressCache
 61{
 62    private readonly ConcurrentDictionary<Location, (TimeSpan InsertionTime, ServiceAddress ServiceAddress, LinkedListNo
 63
 64    // The keys in _cache. The first entries correspond to the most recently added cache entries.
 665    private readonly LinkedList<Location> _cacheKeys = new();
 66
 67    private readonly int _maxCacheSize;
 68
 69    // _mutex protects _cacheKeys and updates to _cache
 670    private readonly object _mutex = new();
 71
 72    public void Remove(Location location)
 1073    {
 1074        lock (_mutex)
 1075        {
 1076            if (_cache.TryRemove(
 1077                location,
 1078                out (TimeSpan InsertionTime, ServiceAddress ServiceAddress, LinkedListNode<Location> Node) entry))
 379            {
 380                _cacheKeys.Remove(entry.Node);
 381            }
 1082        }
 1083    }
 84
 85    public void Set(Location location, ServiceAddress serviceAddress)
 886    {
 887        lock (_mutex)
 888        {
 889            Remove(location); // remove existing cache entry if present
 90
 891            _cache[location] =
 892                (TimeSpan.FromMilliseconds(Environment.TickCount64), serviceAddress, _cacheKeys.AddFirst(location));
 93
 894            if (_cacheKeys.Count == _maxCacheSize + 1)
 195            {
 96                // drop last (oldest) entry
 197                Remove(_cacheKeys.Last!.Value);
 98
 199                Debug.Assert(_cacheKeys.Count == _maxCacheSize); // removed the last entry
 1100            }
 8101        }
 8102    }
 103
 104    public bool TryGetValue(Location location, out (TimeSpan InsertionTime, ServiceAddress ServiceAddress) value)
 7105    {
 106        // no mutex lock: _cache is a concurrent dictionary and it's ok if it's updated while we read it
 107
 7108        if (_cache.TryGetValue(
 7109            location,
 7110            out (TimeSpan InsertionTime, ServiceAddress ServiceAddress, LinkedListNode<Location> Node) entry))
 3111        {
 3112            value.InsertionTime = entry.InsertionTime;
 3113            value.ServiceAddress = entry.ServiceAddress;
 3114            return true;
 115        }
 116        else
 4117        {
 4118            value = default;
 4119            return false;
 120        }
 7121    }
 122
 6123    internal ServerAddressCache(int maxCacheSize)
 6124    {
 6125        Debug.Assert(maxCacheSize > 0);
 6126        _maxCacheSize = maxCacheSize;
 6127        _cache = new(concurrencyLevel: 1, capacity: _maxCacheSize + 1);
 6128    }
 129}
 130
 131/// <summary>A decorator that adds event source logging to a server address cache.</summary>
 132internal class LogServerAddressCacheDecorator : IServerAddressCache
 133{
 134    private readonly IServerAddressCache _decoratee;
 135    private readonly ILogger _logger;
 136
 137    public void Remove(Location location)
 138    {
 139        _decoratee.Remove(location);
 140        _logger.LogRemovedEntry(location.Kind, location);
 141    }
 142
 143    public void Set(Location location, ServiceAddress serviceAddress)
 144    {
 145        _decoratee.Set(location, serviceAddress);
 146        _logger.LogSetEntry(location.Kind, location, serviceAddress);
 147    }
 148
 149    public bool TryGetValue(
 150        Location location,
 151        out (TimeSpan InsertionTime, ServiceAddress ServiceAddress) value)
 152    {
 153        if (_decoratee.TryGetValue(location, out value))
 154        {
 155            _logger.LogFoundEntry(location.Kind, location, value.ServiceAddress);
 156            return true;
 157        }
 158        else
 159        {
 160            return false;
 161        }
 162    }
 163
 164    internal LogServerAddressCacheDecorator(IServerAddressCache decoratee, ILogger logger)
 165    {
 166        _decoratee = decoratee;
 167        _logger = logger;
 168    }
 169}