From 35c0f02838e8d693f9af6aeddb72138af4561cdf Mon Sep 17 00:00:00 2001 From: shimoncohen Date: Fri, 7 Aug 2026 17:14:09 +0300 Subject: [PATCH] perf: de-parallelize upscale ancestor lookup to avoid thread-pool starvation InternalGetLastExistingTile fetched all ancestor coords via an unbounded Parallel.ForEachAsync and blocked on .Result. When called from inside the service-level Parallel.ForEach merge loop this nests parallelism and, via the sync-over-async .Result, blocks an outer worker thread while inner Task.Run items compete for the same thread pool -> starvation under load. The inner GetTile calls also serialize on the source connection, so the concurrency bought little. Replace with a sequential highest-zoom-first loop that short-circuits on the first existing tile. Same result (closest ancestor), fewer GetTile calls, no nesting, no sync-over-async. Co-Authored-By: Claude Opus 4.8 (1M context) --- MergerLogic/DataTypes/Data.cs | 37 +++++++++-------------------------- 1 file changed, 9 insertions(+), 28 deletions(-) diff --git a/MergerLogic/DataTypes/Data.cs b/MergerLogic/DataTypes/Data.cs index ea6bf8d4..8f196983 100644 --- a/MergerLogic/DataTypes/Data.cs +++ b/MergerLogic/DataTypes/Data.cs @@ -3,7 +3,6 @@ using MergerLogic.Utils; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; -using System.Collections.Concurrent; using System.Diagnostics; using System.Reflection; using System.Runtime.Serialization; @@ -215,36 +214,18 @@ protected virtual Extent GetExtent() coordsList.Add(new Coord(i, baseTileX, baseTileY)); } - // Async method to request all tiles that can be used for "upscale" concurrently - var getUpscaleTiles = async delegate (Coord[] coordsArray) + // coordsList runs highest zoom first, so the first hit is the closest ancestor to upscale from. + foreach (Coord coord in coordsList) { - ConcurrentDictionary zOrderToTileDictionary = new ConcurrentDictionary(); - // get all tiles concurrently - await Parallel.ForEachAsync(coordsArray, async (coord, cancellationToken) => + Tile? tile = this.Utils.GetTile(coord.Z, coord.X, coord.Y); + if (tile != null) { - await Task.Run(() => - { - Tile? tile = this.Utils.GetTile(coord.Z, coord.X, coord.Y); - if (tile != null) - { - zOrderToTileDictionary.TryAdd(coord.Z, tile); - } - }, cancellationToken); - }); - return zOrderToTileDictionary.ToArray(); - }; - var response = getUpscaleTiles(coordsList.ToArray()); - var tilesResponseArray = response.Result; - if (tilesResponseArray.Length == 0) - { - return null; + this._logger.LogDebug($"[{MethodBase.GetCurrentMethod()?.Name}] ended, lastTile: z:{tile.Z}, x:{tile.X}, y:{tile.Y}"); + return tile; + } } - // Get first valid tile that can be upscaled - var orderedTilesArray = tilesResponseArray.OrderBy(kvp => kvp.Key); - Tile? lastTile = orderedTilesArray.Last().Value; - string message = lastTile == null ? "null" : $"z:{lastTile.Z}, x:{lastTile.X}, y:{lastTile.Y}"; - this._logger.LogDebug($"[{MethodBase.GetCurrentMethod()?.Name}] ended, lastTile: {message}"); - return lastTile; + this._logger.LogDebug($"[{MethodBase.GetCurrentMethod()?.Name}] ended, lastTile: null"); + return null; } public bool TileExists(Tile tile)