ee5ef7e12a
Age stale pre-delivery attempts to failed and in-delivery attempts to uncertain without provider I/O. Notify each owner with content-free guidance and keep recovery idempotent across replicas and restarts.
45 lines
1.6 KiB
C#
45 lines
1.6 KiB
C#
namespace JobTrackerApi.Services;
|
|
|
|
public sealed class EmailSendAttemptRecoveryHostedService(
|
|
IServiceScopeFactory scopes,
|
|
IStartupReadiness startupReadiness,
|
|
ILogger<EmailSendAttemptRecoveryHostedService> logger) : BackgroundService
|
|
{
|
|
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
|
|
{
|
|
await startupReadiness.WaitUntilReadyAsync(stoppingToken);
|
|
while (!stoppingToken.IsCancellationRequested)
|
|
{
|
|
try
|
|
{
|
|
await using var scope = scopes.CreateAsyncScope();
|
|
var result = await scope.ServiceProvider.GetRequiredService<EmailSendAttemptStore>()
|
|
.ReconcileAbandonedAsync(stoppingToken);
|
|
if (result.FailedPending > 0 || result.UncertainSending > 0)
|
|
{
|
|
logger.LogWarning(
|
|
"Recovered abandoned email attempts: failedPending={FailedPending}, uncertainSending={UncertainSending}. No provider retry was attempted.",
|
|
result.FailedPending, result.UncertainSending);
|
|
}
|
|
}
|
|
catch (OperationCanceledException) when (stoppingToken.IsCancellationRequested)
|
|
{
|
|
break;
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
logger.LogError(ex, "Email send-attempt recovery failed; provider delivery was not attempted.");
|
|
}
|
|
|
|
try
|
|
{
|
|
await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
|
|
}
|
|
catch (OperationCanceledException)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|