From a9046e957b41b32f76c45c7ea85d7c4b41b3c71a Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 17 Aug 2026 15:06:24 +0200 Subject: [PATCH] feat(infrastructure): add TriggerSync to EmailSyncWorker for on-demand immediate sync; fix cancellation propagation after delay --- .../Services/Background/EmailSyncWorker.cs | 24 ++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/Background/EmailSyncWorker.cs b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/Background/EmailSyncWorker.cs index 5b311fe..e931de8 100644 --- a/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/Background/EmailSyncWorker.cs +++ b/src/infrastructure/DigitalData.MessagingService.Infrastructure/Services/Background/EmailSyncWorker.cs @@ -14,6 +14,20 @@ public class EmailSyncWorker(IOptions Options, IServicePro { private readonly string DefaultFolder = "INBOX"; + /// + /// Signals the current to complete immediately, + /// causing the sync loop to start the next cycle without waiting. + /// A new TCS is created at the start of each delay so repeated triggers work correctly. + /// + private volatile TaskCompletionSource _syncTrigger = new(TaskCreationOptions.RunContinuationsAsynchronously); + + /// + /// Triggers an immediate sync cycle by completing the current delay early. + /// Safe to call from any thread or HTTP request at any time. + /// If a sync is already running, the trigger is ignored — the next cycle starts normally. + /// + public void TriggerSync() => _syncTrigger.TrySetResult(true); + protected override async Task ExecuteAsync(CancellationToken stoppingToken) { await UpsertSeedEmailAccount(stoppingToken); @@ -33,7 +47,15 @@ public class EmailSyncWorker(IOptions Options, IServicePro await SyncAccountAsync(account, imapService, pop3Service, stoppingToken); } - await Task.Delay(interval, stoppingToken).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); + // Reset trigger before waiting so any TriggerSync() call during the delay is caught + _syncTrigger = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + + var delay = Task.Delay(interval, stoppingToken); + var triggered = _syncTrigger.Task; + await Task.WhenAny(delay, triggered).ConfigureAwait(false); + + // Propagate cancellation if the host is stopping + stoppingToken.ThrowIfCancellationRequested(); } }