fix(management): purge terminal PendingSecuredWrites rows on the list sweep after a retention window (review 2026-10-21 08/G3)
This commit is contained in:
@@ -1860,9 +1860,56 @@ public class ManagementActor : ReceiveActor
|
||||
await TryExpireIfStaleAsync(sp, repo, row);
|
||||
}
|
||||
|
||||
await TryPurgeTerminalSecuredWritesAsync(sp, repo);
|
||||
|
||||
return new SecuredWriteListResult(rows.Select(ToSecuredWriteDto).ToList(), totalCount);
|
||||
}
|
||||
|
||||
// Last terminal-purge pass on this node (UTC ticks); throttles the purge that rides the
|
||||
// list sweep to once per SecuredWriteTerminalPurgeInterval.
|
||||
private static long _lastSecuredWritePurgeTicks;
|
||||
|
||||
private const int SecuredWritePurgeBatchSize = 1000;
|
||||
|
||||
/// <summary>
|
||||
/// Purges decided/executed secured-write rows older than
|
||||
/// <see cref="ManagementServiceOptions.SecuredWriteTerminalRetention"/> (review 2026-10-21
|
||||
/// 08/G3 — the table used to grow forever and the list's offset paging scanned it all).
|
||||
/// Rides the same opportunistic list sweep as Pending expiry rather than a dedicated
|
||||
/// timer, throttled per node; one bounded batch per pass, so a backlog converges over
|
||||
/// successive passes. Both central nodes may purge concurrently — the set-based delete is
|
||||
/// idempotent. Best-effort: a failure is logged and never fails the list.
|
||||
/// </summary>
|
||||
private static async Task TryPurgeTerminalSecuredWritesAsync(IServiceProvider sp, ISecuredWriteRepository repo)
|
||||
{
|
||||
var options = sp.GetService<IOptions<ManagementServiceOptions>>()?.Value ?? new ManagementServiceOptions();
|
||||
var retention = options.SecuredWriteTerminalRetention;
|
||||
if (retention <= TimeSpan.Zero)
|
||||
return;
|
||||
|
||||
var now = DateTime.UtcNow;
|
||||
var last = Interlocked.Read(ref _lastSecuredWritePurgeTicks);
|
||||
if (options.SecuredWriteTerminalPurgeInterval > TimeSpan.Zero
|
||||
&& now.Ticks - last < options.SecuredWriteTerminalPurgeInterval.Ticks)
|
||||
return;
|
||||
// One winner per interval on this node; a concurrent list skips the pass.
|
||||
if (Interlocked.CompareExchange(ref _lastSecuredWritePurgeTicks, now.Ticks, last) != last)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
var deleted = await repo.PurgeTerminalAsync(now - retention, SecuredWritePurgeBatchSize);
|
||||
if (deleted > 0)
|
||||
sp.GetService<ILogger<ManagementActor>>()?.LogInformation(
|
||||
"Purged {Count} terminal secured-write row(s) older than {Retention}.", deleted, retention);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
sp.GetService<ILogger<ManagementActor>>()?.LogWarning(
|
||||
ex, "Best-effort terminal secured-write purge failed; the list is unaffected.");
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================================================
|
||||
// Site handlers
|
||||
// ========================================================================
|
||||
|
||||
Reference in New Issue
Block a user