fix(notifications): Notify.Send enqueues unbounded (maxRetries 0) and defers to sweep — no 30s script-thread block, no stranding

This commit is contained in:
Joseph Doherty
2026-07-08 20:56:09 -04:00
parent e2944d201c
commit 4bdd5f0e4a
4 changed files with 36 additions and 3 deletions
@@ -317,4 +317,27 @@ public class NotifyHelperTests : TestKit, IAsyncLifetime, IDisposable
// Not at central, not in the site buffer → genuinely unknown, NOT Forwarding.
Assert.Equal("Unknown", status.Status);
}
[Fact]
public async Task NotifySend_BuffersUnbounded_AndNeverInvokesForwarderInline()
{
// Task 13: Notify.Send must buffer with maxRetries: 0 (never park for retry
// exhaustion) and deferToSweep: true (never run the forwarder's central Ask
// inline on the scarce script thread). A central outage must return in
// milliseconds, and the row must not strand behind operator unparking.
var inlineInvoked = false;
_saf.RegisterDeliveryHandler(StoreAndForwardCategory.Notification,
_ => { inlineInvoked = true; throw new TimeoutException("central down"); });
var commProbe = CreateTestProbe();
var notify = CreateHelper(commProbe.Ref);
var notificationId = await notify.To("ops-list").Send("subject", "hello");
Assert.False(inlineInvoked); // pre-fix: Send ran the forwarder Ask inline
var row = await _saf.GetMessageByIdAsync(notificationId);
Assert.NotNull(row);
Assert.Equal(0, row!.MaxRetries); // pre-fix: DefaultMaxRetries → parked after a bounded outage
Assert.Equal(StoreAndForwardMessageStatus.Pending, row.Status);
}
}