perf(store-and-forward): short-circuit a (category,target) lane after its first transient failure per sweep

This commit is contained in:
Joseph Doherty
2026-07-08 20:06:06 -04:00
parent 03dcd55958
commit 1459f9407f
2 changed files with 76 additions and 7 deletions
@@ -638,4 +638,52 @@ public class StoreAndForwardServiceTests : IAsyncLifetime, IDisposable
await _service.RetryPendingMessagesAsync(); // must not throw
Assert.Equal(0, delivered);
}
// ── Task 8 (arch review 02, Performance): per-target short-circuit ──
// After the first transient failure to a (category, target) the remaining
// messages for that pair are skipped this sweep — N dead-target messages
// cost one timeout, not N. Skipped rows keep their RetryCount.
[Fact]
public async Task RetrySweep_ShortCircuitsTarget_AfterFirstTransientFailure()
{
var attemptsByTarget = new Dictionary<string, int>();
_service.RegisterDeliveryHandler(StoreAndForwardCategory.ExternalSystem, msg =>
{
attemptsByTarget[msg.Target] = attemptsByTarget.GetValueOrDefault(msg.Target) + 1;
if (msg.Target == "dead-target") throw new TimeoutException("down");
return Task.FromResult(true);
});
for (var i = 0; i < 3; i++)
await _service.EnqueueAsync(StoreAndForwardCategory.ExternalSystem, "dead-target", "{}",
attemptImmediateDelivery: false, retryInterval: TimeSpan.Zero);
await _service.EnqueueAsync(StoreAndForwardCategory.ExternalSystem, "healthy-target", "{}",
attemptImmediateDelivery: false, retryInterval: TimeSpan.Zero);
await _service.RetryPendingMessagesAsync();
Assert.Equal(1, attemptsByTarget["dead-target"]); // pre-fix: 3
Assert.Equal(1, attemptsByTarget["healthy-target"]); // healthy lane unaffected
}
[Fact]
public async Task RetrySweep_SkippedMessages_DoNotAccrueRetryCount()
{
_service.RegisterDeliveryHandler(StoreAndForwardCategory.ExternalSystem,
_ => throw new TimeoutException("down"));
var id1 = (await _service.EnqueueAsync(StoreAndForwardCategory.ExternalSystem, "dead", "{}",
maxRetries: 5, attemptImmediateDelivery: false, retryInterval: TimeSpan.Zero)).MessageId;
var id2 = (await _service.EnqueueAsync(StoreAndForwardCategory.ExternalSystem, "dead", "{}",
maxRetries: 5, attemptImmediateDelivery: false, retryInterval: TimeSpan.Zero)).MessageId;
await _service.RetryPendingMessagesAsync();
var r1 = (await _service.GetMessageByIdAsync(id1))!.RetryCount;
var r2 = (await _service.GetMessageByIdAsync(id2))!.RetryCount;
// Exactly one was attempted (RetryCount 1); the other was skipped by the
// short-circuit, its RetryCount untouched (0). Order-independent so the
// assertion doesn't depend on created_at tie-breaking.
Assert.Equal(new[] { 0, 1 }, new[] { r1, r2 }.OrderBy(x => x).ToArray());
}
}