fix(health): evict deleted sites from the aggregator on the periodic site refresh

This commit is contained in:
Joseph Doherty
2026-07-08 16:27:38 -04:00
parent 87c7255912
commit d962c77bb7
7 changed files with 99 additions and 3 deletions
@@ -196,6 +196,20 @@ public class CentralHealthAggregator : BackgroundService, ICentralHealthAggregat
return state;
}
/// <inheritdoc />
public void PruneUnknownSites(IReadOnlyCollection<string> knownSiteIds)
{
var known = new HashSet<string>(knownSiteIds, StringComparer.Ordinal);
foreach (var siteId in _siteStates.Keys)
{
if (siteId == CentralHealthReportLoop.CentralSiteId || known.Contains(siteId))
continue;
if (_siteStates.TryRemove(siteId, out _))
_logger.LogInformation(
"Site {SiteId} evicted from health aggregator (no longer configured)", siteId);
}
}
/// <inheritdoc />
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
@@ -39,4 +39,16 @@ public interface ICentralHealthAggregator
/// <param name="siteId">The string identifier of the site to look up.</param>
/// <returns>The <see cref="SiteHealthState"/> for the site, or null if not yet seen.</returns>
SiteHealthState? GetSiteState(string siteId);
/// <summary>
/// Removes tracked sites not in <paramref name="knownSiteIds"/> (i.e. deleted
/// from configuration). The synthetic central id is always retained.
/// Self-healing: called from the CentralCommunicationActor's periodic site
/// refresh, so a deleted site disappears within one refresh interval without
/// needing a dedicated deletion event. Without this the in-memory state only
/// ever grew, leaving a deleted site as a permanently-offline dashboard tile
/// (and a live KPI sample source) forever.
/// </summary>
/// <param name="knownSiteIds">The identifiers of all currently-configured sites.</param>
void PruneUnknownSites(IReadOnlyCollection<string> knownSiteIds);
}