fix(historian-gateway): cancellation-safe alarm writer + dispose-safe outbox + provisioner polish + outbox tests
I-1: GatewayAlarmHistorianWriter no longer dead-letters events cancelled mid-drain at shutdown. WriteBatchAsync short-circuits remaining events to RetryPlease once cancellation is requested, and SendOneAsync catches OperationCanceledException (when the token is cancelled) -> RetryPlease, so in-flight events stay queued instead of being permanently dropped. I-2: FasterLogHistorizationOutbox.Dispose now guards the awaited periodic loop with a broad catch (Exception) after the OperationCanceledException catch, so a non-Faster teardown fault (e.g. ObjectDisposedException) can never escape Dispose. M-1: GatewayTagProvisioner skips the empty EnsureTags round-trip when every request is non-historizable (early return). M-2: GatewayTagProvisioner handles plain shutdown cancellation quietly (Debug, not Warning), counting the unsent batch as Failed, never throwing. M-3/M-4: Added remove-last-entry (TailAddress truncation branch) and FIFO implicit-ack (RemoveAsync acks up to and including the target) durability tests, both reopen-and-survive. M-5: Clarifying comment in RecoverState on the transient over-capacity rebuild after a crash between append-commit and drop-truncation-commit. Claude-Session: https://claude.ai/code/session_012SDSQ3AcaXqPcBtDESBRii
This commit is contained in:
+17
@@ -174,6 +174,23 @@ public sealed class GatewayAlarmHistorianWriterTests
|
||||
Assert.Equal(HistorianWriteOutcome.PermanentFail, outcomes[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Cancellation_mid_drain_is_RetryPlease_not_PermanentFail()
|
||||
{
|
||||
// Shutdown mid-drain: a cancelled token must NOT dead-letter in-flight events (silent data
|
||||
// loss). Every outcome is RetryPlease (stays queued for next startup), WriteBatchAsync never
|
||||
// throws, and the gateway is not called with a cancelled token (short-circuited up front).
|
||||
using var cts = new CancellationTokenSource();
|
||||
await cts.CancelAsync();
|
||||
var fake = new FakeHistorianGatewayClient { SendEventThrows = new OperationCanceledException() };
|
||||
|
||||
var outcomes = await Writer(fake).WriteBatchAsync(new[] { Evt("A"), Evt("B") }, cts.Token);
|
||||
|
||||
Assert.Equal(2, outcomes.Count);
|
||||
Assert.All(outcomes, o => Assert.Equal(HistorianWriteOutcome.RetryPlease, o));
|
||||
Assert.Equal(0, fake.SendEventCallCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task Empty_batch_returns_empty()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user