namespace ScadaLink.Host;
///
/// Bounded retry-with-backoff for startup preconditions.
///
/// Host-010 / REQ-HOST-4a: a Central node applies/validates database migrations
/// before the host begins serving traffic. In container orchestration the database
/// and the app frequently start together, so the database may be briefly
/// unreachable. Rather than crashing the process on the first connection failure,
/// the migration step is wrapped in this bounded exponential backoff: it tolerates a
/// short outage and only fails fatally once attempts are exhausted.
///
public static class StartupRetry
{
public static async Task ExecuteWithRetryAsync(
string operationName,
Func operation,
int maxAttempts,
TimeSpan initialDelay,
ILogger logger,
CancellationToken cancellationToken = default)
{
var delay = initialDelay;
for (var attempt = 1; ; attempt++)
{
cancellationToken.ThrowIfCancellationRequested();
try
{
await operation();
if (attempt > 1)
logger.LogInformation(
"Startup operation '{Operation}' succeeded on attempt {Attempt}.",
operationName, attempt);
return;
}
catch (Exception ex) when (attempt < maxAttempts)
{
logger.LogWarning(ex,
"Startup operation '{Operation}' failed on attempt {Attempt}/{MaxAttempts}; " +
"retrying in {Delay}.",
operationName, attempt, maxAttempts, delay);
await Task.Delay(delay, cancellationToken);
// Exponential backoff, capped so the total wait stays bounded.
delay = TimeSpan.FromTicks(Math.Min(delay.Ticks * 2, TimeSpan.FromSeconds(30).Ticks));
}
}
}
}