using Microsoft.Extensions.Options; public sealed class ReconciliationScheduler : BackgroundService { private readonly IServiceScopeFactory _scopes; private readonly ReconciliationJobOptions _opts; private readonly ILogger _logger; public ReconciliationScheduler( IServiceScopeFactory scopes, IOptions opts, ILogger logger) { _scopes = scopes; _opts = opts.Value; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // Wait for RabbitMQ topology and migrations before first cycle. await Task.Delay(TimeSpan.FromSeconds(15), stoppingToken); _logger.LogInformation( "ReconciliationScheduler started — interval {IntervalMinutes} min", _opts.IntervalMinutes); using var timer = new PeriodicTimer(TimeSpan.FromMinutes(_opts.IntervalMinutes)); while (await timer.WaitForNextTickAsync(stoppingToken)) { _logger.LogInformation("[RECONCILIATION] Starting reconciliation cycle"); await RunCheckAsync( check => check.RunAsync(stoppingToken), stoppingToken); await RunCheckAsync( check => check.RunAsync(stoppingToken), stoppingToken); await RunCheckAsync( check => check.RunAsync(stoppingToken), stoppingToken); _logger.LogInformation("[RECONCILIATION] Cycle complete"); } } private async Task RunCheckAsync(Func> run, CancellationToken ct) where T : notnull { try { await using var scope = _scopes.CreateAsyncScope(); var check = scope.ServiceProvider.GetRequiredService(); var count = await run(check); _logger.LogInformation( "[RECONCILIATION] {Check} — new alerts: {Count}", typeof(T).Name, count); } catch (Exception ex) { _logger.LogError(ex, "[RECONCILIATION] {Check} failed", typeof(T).Name); } } }