< Summary

Class:Itinero.Network.Search.Islands.RoutingNetworkIslandManager
Assembly:Itinero
File(s):/home/runner/work/routing2/routing2/src/Itinero/Network/Search/Islands/RoutingNetworkIslandManager.cs
Covered lines:123
Uncovered lines:14
Coverable lines:137
Total lines:282
Line coverage:89.7% (123 of 137)
Covered branches:24
Total branches:28
Branch coverage:85.7% (24 of 28)
Tag:268_34224473380

Metrics

MethodBranch coverage Cyclomatic complexity Line coverage
.ctor(...)100%1100%
GetBuildSerialiser(...)100%1100%
.ctor(...)100%1100%
IsEdgeOnIsland(...)100%1100%
IsEdgeOnIsland(...)62.5%881.25%
GetOrCreateDirectedGraph(...)100%2100%
get_MaxIslandSize()100%1100%
TryGetIslandsFor(...)100%10%
GetIslandsFor(...)100%2100%
IsMainN(...)100%10100%
BuildForTileAsync()100%4100%
RemoveTileInProgress(...)100%1100%
Clone()50%276.92%

File(s)

/home/runner/work/routing2/routing2/src/Itinero/Network/Search/Islands/RoutingNetworkIslandManager.cs

#LineLine coverage
 1using System;
 2using System.Collections.Concurrent;
 3using System.Collections.Generic;
 4using System.Diagnostics.CodeAnalysis;
 5using System.Threading;
 6using System.Threading.Tasks;
 7using Itinero.Profiles;
 8
 9namespace Itinero.Network.Search.Islands;
 10
 11internal class RoutingNetworkIslandManager
 12{
 60813    private readonly Dictionary<(string profile, uint tile), Task> _tilesInProgress = new();
 60814    private readonly ReaderWriterLockSlim _tilesInProgressLock = new();
 15    private readonly Dictionary<string, Islands> _islands;
 60816    private readonly Dictionary<(string profile, IslandKind kind), IslandDirectedGraph> _directedGraphs = new();
 60817    private readonly ReaderWriterLockSlim _islandsLock = new();
 18
 19    // Per-profile semaphore that serialises IslandClassifier.BuildForTileAsync
 20    // calls against each other for the same profile. The shared Full+NonLocal
 21    // dgs are reset to their initial state at the end of each call (per spec);
 22    // running two classifications for the same profile concurrently would let
 23    // one wipe the other's working state mid-flight. Different profiles still
 24    // run in parallel — each has its own dg pair and its own semaphore.
 60825    private readonly ConcurrentDictionary<string, SemaphoreSlim> _buildSerialisers = new();
 26
 27    internal SemaphoreSlim GetBuildSerialiser(string profileName) =>
 6928        _buildSerialisers.GetOrAdd(profileName, _ => new SemaphoreSlim(1, 1));
 29
 35130    internal RoutingNetworkIslandManager(int maxIslandSize)
 35131    {
 35132        this.MaxIslandSize = maxIslandSize;
 35133        _islands = new();
 35134    }
 35
 25736    private RoutingNetworkIslandManager(int maxIslandSize, Dictionary<string, Islands> islands)
 25737    {
 25738        this.MaxIslandSize = maxIslandSize;
 25739        _islands = islands;
 25740    }
 41
 42    /// <summary>
 43    /// Checks if an edge is on an island using the directed graph.
 44    /// Returns true if island, false if not island, null if inconclusive.
 45    /// </summary>
 46    internal bool? IsEdgeOnIsland(Profile profile, EdgeId edgeId) =>
 747        this.IsEdgeOnIsland(profile.Name, edgeId);
 48
 49    internal bool? IsEdgeOnIsland(string profileName, EdgeId edgeId)
 750    {
 51        try
 752        {
 753            _islandsLock.EnterReadLock();
 54
 55            // Snapping is a Full-classification concern, so the existing
 56            // single-DG semantics route through the Full DG.
 757            if (!_directedGraphs.TryGetValue((profileName, IslandKind.Full), out var dg))
 558                return null;
 59
 260            if (!_islands.TryGetValue(profileName, out var profileIslands))
 061                return null;
 262            if (profileIslands.IsEdgeOnIsland(edgeId))
 063                return true;
 64
 265            if (dg.IsNotIsland(edgeId))
 066                return false;
 67
 268            return null;
 69        }
 70        finally
 771        {
 772            _islandsLock.ExitReadLock();
 773        }
 774    }
 75
 76    internal IslandDirectedGraph GetOrCreateDirectedGraph(Profile profile, IslandKind kind = IslandKind.Full)
 365977    {
 365978        var key = (profile.Name, kind);
 79        try
 365980        {
 365981            _islandsLock.EnterUpgradeableReadLock();
 82
 723983            if (_directedGraphs.TryGetValue(key, out var dg)) return dg;
 84
 85            try
 7986            {
 7987                _islandsLock.EnterWriteLock();
 88
 7989                dg = new IslandDirectedGraph();
 7990                _directedGraphs[key] = dg;
 7991                return dg;
 92            }
 93            finally
 7994            {
 7995                _islandsLock.ExitWriteLock();
 7996            }
 97        }
 98        finally
 365999        {
 3659100            _islandsLock.ExitUpgradeableReadLock();
 3659101        }
 3659102    }
 103
 2774104    internal int MaxIslandSize { get; }
 105
 106    internal bool TryGetIslandsFor(string profileName, out Islands islands)
 0107    {
 108        try
 0109        {
 0110            _islandsLock.EnterReadLock();
 111
 0112            return _islands.TryGetValue(profileName, out islands);
 113        }
 114        finally
 0115        {
 0116            _islandsLock.ExitReadLock();
 0117        }
 0118    }
 119
 120    internal Islands GetIslandsFor(Profile profile)
 2426121    {
 122        try
 2426123        {
 2426124            _islandsLock.EnterUpgradeableReadLock();
 125
 4808126            if (_islands.TryGetValue(profile.Name, out var islands)) return islands;
 127
 128            try
 44129            {
 44130                _islandsLock.EnterWriteLock();
 131
 44132                islands = new Islands();
 44133                _islands[profile.Name] = islands;
 44134                return islands;
 135            }
 136            finally
 44137            {
 44138                _islandsLock.ExitWriteLock();
 44139            }
 140        }
 141        finally
 2426142        {
 2426143            _islandsLock.ExitUpgradeableReadLock();
 2426144        }
 2426145    }
 146
 147    /// <summary>
 148    /// Returns whether the edge is in the profile's main-N component — the
 149    /// dominant SCC of the N-only subgraph, i.e. the "mainland" without
 150    /// L-edges.
 151    ///
 152    /// <list type="bullet">
 153    /// <item><c>true</c>: edge is in main-N. Default for any edge in a done tile
 154    /// that is neither L-tagged, on an island, nor a non-main-N pocket member.</item>
 155    /// <item><c>false</c>: edge is not in main-N. Either L-tagged (passed in via
 156    /// <paramref name="isLocalAccess"/>), on an island (unreachable in Full), or
 157    /// recorded as a local edge (non-main-N pocket).</item>
 158    /// <item><c>null</c>: classification has not yet produced a verdict for this
 159    /// tile.</item>
 160    /// </list>
 161    ///
 162    /// The L-tag check is tag-driven and resolved by the caller (typically via
 163    /// the cost function's <c>localAccess</c> field on the result of <c>Get</c>),
 164    /// then passed in. The manager itself does not consult any tag storage.
 165    /// </summary>
 166    internal bool? IsMainN(Profile profile, EdgeId edgeId, bool isLocalAccess)
 788377167    {
 168        // L-tagged edge — never main-N, no storage lookup needed.
 788379169        if (isLocalAccess) return false;
 170
 171        try
 788375172        {
 788375173            _islandsLock.EnterReadLock();
 174
 1568830175            if (!_islands.TryGetValue(profile.Name, out var islands)) return null;
 176
 177            // Unreachable in the Full classification → not in main-N.
 7921178            if (islands.IsEdgeOnIsland(edgeId)) return false;
 179
 180            // Non-main-N pocket → not in main-N.
 7920181            if (islands.IsEdgeLocal(edgeId)) return false;
 182
 183            // Tile finished classifying and the edge is in neither set → main-N.
 184            // Otherwise we don't yet know.
 7918185            return islands.GetTileDone(edgeId.TileId) ? true : null;
 186        }
 187        finally
 788375188        {
 788375189            _islandsLock.ExitReadLock();
 788375190        }
 788377191    }
 192
 193    internal async Task BuildForTileAsync(RoutingNetwork network, Profile profile, uint tileId,
 194        CancellationToken cancellationToken)
 7195    {
 196        // queue task, if not done yet.
 197        Task task;
 7198        var started = false;
 199        try
 7200        {
 7201            _tilesInProgressLock.EnterUpgradeableReadLock();
 202
 7203            if (!_tilesInProgress.TryGetValue((profile.Name, tileId), out task))
 7204            {
 205                try
 7206                {
 7207                    _tilesInProgressLock.EnterWriteLock();
 208
 209                    // CancellationToken.None, deliberately: this task is shared with every later
 210                    // caller for the same tile, so it must not carry the token of whoever happened
 211                    // to ask first. Passing that token let one caller going away cancel the work
 212                    // everyone else was waiting on — and, because the entry below was only removed
 213                    // after a successful await, the cancelled task stayed in the dictionary and was
 214                    // handed to every subsequent caller, each of which got an
 215                    // OperationCanceledException for a request it never cancelled. That poisoned
 216                    // the tile for the lifetime of the network. Callers stay cancellable through
 217                    // their own WaitAsync below.
 7218                    task = IslandClassifier.BuildForTileAsync(network, profile, tileId,
 7219                        CancellationToken.None);
 7220                    _tilesInProgress[(profile.Name, tileId)] = task;
 7221                    started = true;
 7222                }
 223                finally
 7224                {
 7225                    _tilesInProgressLock.ExitWriteLock();
 7226                }
 7227            }
 7228        }
 229        finally
 7230        {
 7231            _tilesInProgressLock.ExitUpgradeableReadLock();
 7232        }
 233
 234        // Remove on completion whatever the outcome, so a task that failed is retried by the next
 235        // caller rather than replayed at it forever. Attached outside the locks above: the
 236        // continuation runs inline when the task is already complete, and re-entering the write
 237        // lock on this thread would throw.
 7238        if (started)
 7239        {
 14240            _ = task.ContinueWith(_ => this.RemoveTileInProgress(profile.Name, tileId),
 7241                TaskContinuationOptions.ExecuteSynchronously);
 7242        }
 243
 244        // Await the shared task, but only for as long as this caller is still interested. Giving up
 245        // here does not stop the classification for anyone else.
 7246        await task.WaitAsync(cancellationToken);
 7247    }
 248
 249    private void RemoveTileInProgress(string profileName, uint tileId)
 7250    {
 251        try
 7252        {
 7253            _tilesInProgressLock.EnterWriteLock();
 254
 7255            _tilesInProgress.Remove((profileName, tileId));
 7256        }
 257        finally
 7258        {
 7259            _tilesInProgressLock.ExitWriteLock();
 7260        }
 7261    }
 262
 263    internal RoutingNetworkIslandManager Clone()
 257264    {
 265        try
 257266        {
 257267            _islandsLock.EnterReadLock();
 268
 257269            var islands = new Dictionary<string, Islands>();
 771270            foreach (var (profileName, profileIslands) in _islands)
 0271            {
 0272                islands[profileName] = profileIslands.Clone();
 0273            }
 274
 257275            return new RoutingNetworkIslandManager(this.MaxIslandSize, islands);
 276        }
 277        finally
 257278        {
 257279            _islandsLock.ExitReadLock();
 257280        }
 257281    }
 282}