using Microsoft.Extensions.Options; using QMax.Api.Configuration; namespace QMax.Api.Services; public sealed class MaxOutboxWorker( IServiceScopeFactory scopeFactory, IOptions options, ILogger logger) : BackgroundService { private readonly QMaxOptions _options = options.Value; protected override async Task ExecuteAsync(CancellationToken stoppingToken) { var delay = TimeSpan.FromSeconds(Math.Max(1, _options.MaxOutboxPollIntervalSeconds)); await Task.WhenAll( RunOutboxLoopAsync( "message outbox", delay, (outbox, cancellationToken) => outbox.ProcessPendingMessagesOnceAsync(cancellationToken), stoppingToken), RunOutboxLoopAsync( "chat command outbox", delay, (outbox, cancellationToken) => outbox.ProcessPendingChatActionsOnceAsync(cancellationToken), stoppingToken)); } private async Task RunOutboxLoopAsync( string workerName, TimeSpan delay, Func> process, CancellationToken stoppingToken) { using var timer = new PeriodicTimer(delay); while (!stoppingToken.IsCancellationRequested) { try { await timer.WaitForNextTickAsync(stoppingToken); } catch (OperationCanceledException) { break; } try { await using var scope = scopeFactory.CreateAsyncScope(); var outbox = scope.ServiceProvider.GetRequiredService(); await process(outbox, stoppingToken); } catch (Exception ex) { logger.LogWarning(ex, "QMAX {WorkerName} tick failed.", workerName); } } } }