From 2fdc8982aaca141693a59be13f6337163a9ecd9d Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 2 Nov 2026 10:10:55 +0100 Subject: [PATCH] feat(infrastructure): publish worker status to health checks ConsumerInitWorker marks ConsumerInitHealthCheck as unhealthy when the consumer pool fails to start, so callers receive HTTP 503 instead of a silently degraded service. EmailSyncWorker now reports its startup interval and the result of every sync cycle. SyncAccountAsync returns whether the account failed, letting the worker count errors per cycle and expose them through EmailSyncWorkerHealthCheck. --- .../Services/Background/ConsumerInitWorker.cs | 10 ++++++++-- .../Services/Background/EmailSyncWorker.cs | 18 ++++++++++++++---- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/Background/ConsumerInitWorker.cs b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/Background/ConsumerInitWorker.cs index f6703f5..046877b 100644 --- a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/Background/ConsumerInitWorker.cs +++ b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/Background/ConsumerInitWorker.cs @@ -11,7 +11,11 @@ namespace DigitalData.MessagingService.Infrastructure.Services.Background; /// Leverages a push-based, event-driven RabbitMQ consumer to eliminate polling overhead. /// Email account configuration is resolved exclusively from application settings; no database access is performed. /// -public class ConsumerInitWorker(SendingEmailConsumerPool ConsumerPool, ILogger Logger, IOptions RabbitMqOptions) : BackgroundService +public class ConsumerInitWorker( + SendingEmailConsumerPool ConsumerPool, + ILogger Logger, + IOptions RabbitMqOptions, + ConsumerInitHealthCheck HealthCheck) : BackgroundService { protected override async Task ExecuteAsync(CancellationToken stoppingToken) { @@ -21,10 +25,12 @@ public class ConsumerInitWorker(SendingEmailConsumerPool ConsumerPool, ILogger Options, IServiceProvider Provider, ILogger Logger, IMemoryCache Cache) : BackgroundService, IEmailSyncService +public class EmailSyncWorker(IOptions Options, IServiceProvider Provider, ILogger Logger, IMemoryCache Cache, EmailSyncWorkerHealthCheck HealthCheck) : BackgroundService, IEmailSyncService { private static string ForcedSyncDateCacheKey => $"{nameof(EmailSyncWorker)}_TriggerSync"; @@ -44,6 +44,7 @@ public class EmailSyncWorker(IOptions Options, IServicePro await UpsertSeedEmailAccount(stoppingToken); var interval = TimeSpan.FromSeconds(Options.Value.SyncIntervalSeconds); + HealthCheck.OnWorkerStarted(interval); while (!stoppingToken.IsCancellationRequested) { @@ -53,11 +54,15 @@ public class EmailSyncWorker(IOptions Options, IServicePro var imapService = scope.ServiceProvider.GetRequiredService(); var pop3Service = scope.ServiceProvider.GetRequiredService(); + int cycleErrorCount = 0; foreach (var account in await emailAccountRepo.GetAllAsync(stoppingToken)) { - await SyncAccountAsync(account, imapService, pop3Service, stoppingToken); + var hadError = await SyncAccountAsync(account, imapService, pop3Service, stoppingToken); + if (hadError) cycleErrorCount++; } + HealthCheck.OnCycleCompleted(cycleErrorCount); + // Reset trigger before waiting so any TriggerSync() call during the delay is caught _syncTrigger = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -70,14 +75,15 @@ public class EmailSyncWorker(IOptions Options, IServicePro } } - private async Task SyncAccountAsync( + /// true if the sync failed for this account; otherwise false. + private async Task SyncAccountAsync( EmailAccount account, IImapEmailService imapService, IPop3EmailService pop3Service, CancellationToken stoppingToken) { if (account.IncomingProtocol == IncomingProtocol.None) - return; + return false; Logger.LogDebug( "Email sync started. Account={Username}, Protocol={Protocol}.", @@ -100,12 +106,16 @@ public class EmailSyncWorker(IOptions Options, IServicePro Logger.LogDebug( "Email sync completed. Account={Username}, Protocol={Protocol}, Processed={Processed}, Failed={Failed}.", account.Username, account.IncomingProtocol, result.ProcessedCount, result.FailedCount); + + return false; } catch (Exception ex) { Logger.LogError(ex, "Email sync failed. Account={Username}, Protocol={Protocol}.", account.Username, account.IncomingProtocol); + + return true; } }