using System; using Svrnty.CQRS.Events.Abstractions.Storage; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Svrnty.CQRS.Events.Abstractions; namespace Svrnty.CQRS.Events.Services; /// /// Background service that periodically cleans up old idempotency records. /// /// /// This service cleans up: /// - Old processed event records (older than retention period) /// - Expired idempotency locks (automatically handled by the store) /// public class IdempotencyCleanupService : BackgroundService { private readonly IIdempotencyStore _idempotencyStore; private readonly ILogger _logger; // Configuration - these could be made configurable via options private readonly TimeSpan _cleanupInterval = TimeSpan.FromHours(1); private readonly TimeSpan _retentionPeriod = TimeSpan.FromDays(7); public IdempotencyCleanupService( IIdempotencyStore idempotencyStore, ILogger logger) { _idempotencyStore = idempotencyStore ?? throw new ArgumentNullException(nameof(idempotencyStore)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation( "Idempotency cleanup service started (Interval: {Interval}, Retention: {Retention})", _cleanupInterval, _retentionPeriod); while (!stoppingToken.IsCancellationRequested) { try { await Task.Delay(_cleanupInterval, stoppingToken); if (stoppingToken.IsCancellationRequested) break; await PerformCleanupAsync(stoppingToken); } catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested) { // Normal shutdown break; } catch (Exception ex) { _logger.LogError(ex, "Error during idempotency cleanup"); // Continue running despite errors } } _logger.LogInformation("Idempotency cleanup service stopped"); } private async Task PerformCleanupAsync(CancellationToken cancellationToken) { var olderThan = DateTimeOffset.UtcNow.Subtract(_retentionPeriod); _logger.LogDebug("Starting idempotency cleanup (deleting records older than {OlderThan})", olderThan); var deletedCount = await _idempotencyStore.CleanupAsync(olderThan, cancellationToken); if (deletedCount > 0) { _logger.LogInformation("Cleaned up {DeletedCount} old idempotency records", deletedCount); } else { _logger.LogDebug("No old idempotency records to clean up"); } } }