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