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)); } } } }