fix(runtime): the passive half of a LocalDb pair stands its alarm drain down for its mesh partner (review R6 MINOR-4)
The drain gate's 'is my queue-sharing peer Primary?' used only the host of
LocalDb:Replication:PeerAddress, which the passive half (ApiKey +
SyncListenPort) does not set - so as Secondary it kept draining beside its
Primary partner and every alarm event reached the historian twice.
DriverHostActor takes a sharesAlarmQueueWithLocal predicate used when no
peer host is known; WithOtOpcUaRuntimeActors builds it
(BuildAlarmQueuePeerPredicate) for a SharesRowsWithPeer node with no
PeerAddress as 'a member of my own cluster-{ClusterId} mesh other than
me'. A node sharing no rows, or with no mesh role, gets none and keeps
draining. AlarmHistorian.md / Redundancy.md / pair-replication runbook
updated.
Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
@@ -135,6 +135,43 @@ public sealed class DriverHostActorRoleViewTests : RuntimeActorTestBase
|
||||
AwaitAssert(() => view.Published.ShouldBe([true, false]), duration: Timeout);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Review R6 MINOR-4 — the PASSIVE half of a pair dials nobody (no <c>PeerAddress</c>, so no peer host) but
|
||||
/// holds the same replicated rows, so as Secondary it must stand down for its Primary partner too, which it
|
||||
/// identifies through the queue-sharing predicate (its mesh partner). Before the fix it published
|
||||
/// <c>true</c> here and both halves drained. A Primary the predicate does NOT name is the control: the
|
||||
/// passive half keeps draining, so the deny comes from identifying the partner, not from being Secondary.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void A_passive_secondary_closes_the_view_for_its_mesh_partner_and_only_for_it()
|
||||
{
|
||||
var partner = NodeId.Parse("site-a-1:4053");
|
||||
foreach (var (primary, expected) in new[] { (partner, false), (NodeId.Parse("site-b-1:4053"), true) })
|
||||
{
|
||||
var view = new RecordingRoleView();
|
||||
var host = Sys.ActorOf(DriverHostActor.Props(
|
||||
NewInMemoryDbFactory(),
|
||||
TestNode,
|
||||
ackRouter: null,
|
||||
driverMemberCountProvider: () => 2,
|
||||
redundancyRoleView: view,
|
||||
replicationPeerHost: null,
|
||||
sharesAlarmQueueWithLocal: n => n == partner));
|
||||
|
||||
host.Tell(new RedundancyStateChanged(
|
||||
[
|
||||
new NodeRedundancyState(TestNode, RedundancyRole.Secondary,
|
||||
IsClusterLeader: false, IsDriverPrimary: false, AsOfUtc: DateTime.UtcNow),
|
||||
new NodeRedundancyState(primary, RedundancyRole.Primary,
|
||||
IsClusterLeader: true, IsDriverPrimary: true, AsOfUtc: DateTime.UtcNow),
|
||||
],
|
||||
CorrelationId.NewId()));
|
||||
|
||||
AwaitAssert(() => view.Published.ShouldBe([expected]), duration: Timeout);
|
||||
Sys.Stop(host);
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------- helpers ----------------
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user