From a6a72661e73d0410433ee94c6db535b9bd4e7cbe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?S=C3=B8ren=20Eggert=20Lundsteen=20Olsen?= Date: Fri, 2 Oct 2026 13:08:18 +0200 Subject: [PATCH] Review 1.3: a routine discovery that could not be stored is not retried every 2 seconds When saving a discovery failed in the repository (deadlock victim, lock timeout, connection), LastDiscoveryAt stayed unset and the next round 2 seconds later listed the same instance's databases again, loading the customer's server. The agent now waits at least 15 minutes between routine discoveries of an instance. Co-Authored-By: Claude Opus 5.5 (1M context) --- .../Configuration/AgentRequestService.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/SQLInsight.Agent/Configuration/AgentRequestService.cs b/src/SQLInsight.Agent/Configuration/AgentRequestService.cs index 6f5d4f8..297fe86 100644 --- a/src/SQLInsight.Agent/Configuration/AgentRequestService.cs +++ b/src/SQLInsight.Agent/Configuration/AgentRequestService.cs @@ -41,6 +41,11 @@ public sealed partial class AgentRequestService( public static readonly TimeSpan RequestTimeout = TimeSpan.FromMinutes(2); + /// The least time between two routine discoveries of one instance, also when the last one could not be stored. + public static readonly TimeSpan RoutineRetry = TimeSpan.FromMinutes(15); + + private readonly Dictionary _routineAttempts = []; + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await readiness.Ready.WaitAsync(stoppingToken).ConfigureAwait(false); @@ -172,8 +177,11 @@ public sealed partial class AgentRequestService( /// The routine discovery of the active instance that waited longest, if one is due. private async Task RefreshOneAsync(DateTimeOffset now, CancellationToken cancellationToken) { + // Also waits on the agent's own last attempt: if storing the result fails (the repository), LastDiscoveryAt is + // not set, and without this the same instance would be listed again every round (every 2 seconds). var due = (await repository.GetConfiguredInstancesAsync(cancellationToken).ConfigureAwait(false)) .Where(i => !i.Paused && i.CredentialError is null && (i.LastDiscoveryAt is null || now.UtcDateTime - i.LastDiscoveryAt.Value >= DiscoveryInterval)) + .Where(i => !_routineAttempts.TryGetValue(i.ConfiguredInstanceId, out var attempted) || now - attempted >= RoutineRetry) .OrderBy(i => i.LastDiscoveryAt ?? DateTime.MinValue) .FirstOrDefault(); if (due is null) @@ -181,6 +189,7 @@ public sealed partial class AgentRequestService( return; } + _routineAttempts[due.ConfiguredInstanceId] = now; using var timeout = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken); timeout.CancelAfter(RequestTimeout); try