using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using ZB.MOM.WW.LocalDb.Replication.Internal; namespace ZB.MOM.WW.LocalDb.Replication; /// /// Periodic oplog maintenance: every it /// enforces the backlog/age caps (which flag needs_snapshot when exceeded) and prunes /// expired tombstone row-versions. Runs on BOTH roles — a passive node with a long-dead peer is /// exactly the case where the oplog would otherwise grow unbounded. A failing tick is logged and /// skipped; maintenance never dies. /// public sealed class MaintenanceBackgroundService : BackgroundService { private readonly OplogStore _store; private readonly ReplicationOptions _options; private readonly ILogger _logger; // Rate-limits the caps-exceeded warning to one per flag episode: EnforceCapsAsync keeps // returning true every tick while the backlog stays over cap, which would otherwise flood // the log at the maintenance rate. Reset when a tick comes back clean. private bool _snapshotFlagWarned; internal MaintenanceBackgroundService( OplogStore store, IOptions options, ILoggerFactory loggerFactory) { _store = store; _options = options.Value; _logger = loggerFactory.CreateLogger(); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { // First tick lands one interval after startup (not immediately) to keep startup cheap. while (!stoppingToken.IsCancellationRequested) { try { await Task.Delay(_options.MaintenanceInterval, stoppingToken); } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { break; } try { var snapshotFlagged = await _store.EnforceCapsAsync(stoppingToken); await _store.PruneTombstonesAsync(stoppingToken); if (snapshotFlagged && !_snapshotFlagWarned) { _snapshotFlagWarned = true; _logger.LogWarning( "Oplog backlog/age caps exceeded: pruned to the ceiling and flagged needs_snapshot — the peer must snapshot-resync. Not re-logged until a clean tick."); } else if (!snapshotFlagged) { _snapshotFlagWarned = false; } _logger.LogDebug("Maintenance tick complete (snapshotFlagged={SnapshotFlagged}).", snapshotFlagged); } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { break; } catch (Exception ex) { // Maintenance must never die: log and try again next interval. _logger.LogWarning(ex, "Maintenance tick failed; retrying next interval."); } } } }