fix(saf): standby assembles chunked resync, applies atomically with re-check, acks; N5 race documented (plan R2-02 T6)
This commit is contained in:
@@ -335,6 +335,57 @@ akka {
|
||||
Assert.Equal(3, rest.Sum(c => c.Messages.Count));
|
||||
}
|
||||
|
||||
// ── R2 T6: standby chunk assembly + atomic apply + ack ──
|
||||
|
||||
[Fact]
|
||||
public async Task StandbyNode_AssemblesChunks_AppliesOnce_AndAcks()
|
||||
{
|
||||
await _sfStorage.EnqueueAsync(NewMessage("stale"));
|
||||
var actor = CreateResyncActor(isActive: () => false);
|
||||
var resyncId = "r1";
|
||||
|
||||
actor.Tell(new SfBufferSnapshotChunk(resyncId, 1, 2,
|
||||
new List<StoreAndForwardMessage> { NewMessage("f1") }, false), TestActor);
|
||||
actor.Tell(new SfBufferSnapshotChunk(resyncId, 2, 2,
|
||||
new List<StoreAndForwardMessage> { NewMessage("f2") }, false), TestActor);
|
||||
|
||||
var ack = ExpectMsg<SfBufferResyncAck>(TimeSpan.FromSeconds(5));
|
||||
Assert.Equal(resyncId, ack.ResyncId);
|
||||
Assert.Equal(2, ack.RowCount);
|
||||
await AwaitAssertAsync(async () =>
|
||||
{
|
||||
Assert.Null(await _sfStorage.GetMessageByIdAsync("stale")); // replaced wholesale
|
||||
Assert.NotNull(await _sfStorage.GetMessageByIdAsync("f1"));
|
||||
Assert.NotNull(await _sfStorage.GetMessageByIdAsync("f2"));
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task StandbyNode_NewResyncId_DiscardsStalePartialAssembly()
|
||||
{
|
||||
var actor = CreateResyncActor(isActive: () => false);
|
||||
actor.Tell(new SfBufferSnapshotChunk("old", 1, 2,
|
||||
new List<StoreAndForwardMessage> { NewMessage("orphan") }, false), TestActor);
|
||||
actor.Tell(new SfBufferSnapshotChunk("new", 1, 1,
|
||||
new List<StoreAndForwardMessage> { NewMessage("fresh") }, false), TestActor);
|
||||
|
||||
ExpectMsg<SfBufferResyncAck>(TimeSpan.FromSeconds(5)); // "new" completed
|
||||
await AwaitAssertAsync(async () =>
|
||||
{
|
||||
Assert.NotNull(await _sfStorage.GetMessageByIdAsync("fresh"));
|
||||
Assert.Null(await _sfStorage.GetMessageByIdAsync("orphan")); // stale partial never applied
|
||||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ActiveNode_IgnoresChunks_NeverAcks()
|
||||
{
|
||||
var actor = CreateResyncActor(isActive: () => true);
|
||||
actor.Tell(new SfBufferSnapshotChunk("r", 1, 1,
|
||||
new List<StoreAndForwardMessage> { NewMessage("x") }, false), TestActor);
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
}
|
||||
|
||||
private static StoreAndForwardMessage NewSfMessage(string id) => new()
|
||||
{
|
||||
Id = id,
|
||||
|
||||
Reference in New Issue
Block a user