refactor(notification-outbox): make purge fault-handling symmetric with dispatch
This commit is contained in:
@@ -59,6 +59,8 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
Receive<InternalMessages.DispatchTick>(_ => HandleDispatchTick());
|
||||
Receive<InternalMessages.DispatchComplete>(_ => _dispatching = false);
|
||||
Receive<InternalMessages.PurgeTick>(_ => HandlePurgeTick());
|
||||
// No-op: purge has no in-flight guard to lower, and the outcome is already logged
|
||||
// by the PipeTo projections, so PurgeComplete carries nothing to act on.
|
||||
Receive<InternalMessages.PurgeComplete>(_ => { });
|
||||
Receive<NotificationOutboxQueryRequest>(HandleQuery);
|
||||
Receive<NotificationStatusQuery>(HandleStatusQuery);
|
||||
@@ -297,9 +299,11 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
/// <summary>
|
||||
/// Handles a purge tick by launching an asynchronous sweep that bulk-deletes terminal
|
||||
/// notification rows older than <see cref="NotificationOutboxOptions.TerminalRetention"/>.
|
||||
/// Purges are daily and idempotent, so no in-flight guard is needed; the outcome is piped
|
||||
/// back to <see cref="Self"/> only so a faulted purge is logged on the actor thread and
|
||||
/// never wedges anything.
|
||||
/// Purges are daily and idempotent, so no in-flight guard is needed. <see cref="RunPurgePass"/>
|
||||
/// self-isolates its faults — it logs internally and never faults its task — so the
|
||||
/// success projection is the normal completion path that logs the deleted count. The
|
||||
/// failure projection is kept as a belt-and-braces backup, consistent with
|
||||
/// <see cref="HandleDispatchTick"/>/<see cref="RunDispatchPass"/>.
|
||||
/// </summary>
|
||||
private void HandlePurgeTick()
|
||||
{
|
||||
@@ -324,13 +328,26 @@ public class NotificationOutboxActor : ReceiveActor, IWithTimers
|
||||
/// <summary>
|
||||
/// Runs a single purge sweep: resolves a scoped <see cref="INotificationOutboxRepository"/>
|
||||
/// and bulk-deletes terminal rows created before <paramref name="cutoff"/>, returning the
|
||||
/// deleted count.
|
||||
/// deleted count. The whole body is wrapped in a try/catch so the returned task never
|
||||
/// faults — scope creation, service resolution, and the bulk delete can all throw, and
|
||||
/// self-isolating the fault here keeps the fault-handling strategy symmetric with
|
||||
/// <see cref="RunDispatchPass"/>. On failure the exception is logged and 0 is returned.
|
||||
/// </summary>
|
||||
private async Task<int> RunPurgePass(DateTimeOffset cutoff)
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var repository = scope.ServiceProvider.GetRequiredService<INotificationOutboxRepository>();
|
||||
return await repository.DeleteTerminalOlderThanAsync(cutoff);
|
||||
try
|
||||
{
|
||||
using var scope = _serviceProvider.CreateScope();
|
||||
var repository = scope.ServiceProvider.GetRequiredService<INotificationOutboxRepository>();
|
||||
return await repository.DeleteTerminalOlderThanAsync(cutoff);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Scope/service resolution or the bulk delete faulted; swallow and log so the
|
||||
// returned task completes normally, mirroring RunDispatchPass.
|
||||
_logger.LogError(ex, "Purge sweep failed unexpectedly.");
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user