test(alarms): fix reconnect-drop test to use mailbox-ordering approach
This commit is contained in:
+23
-26
@@ -98,38 +98,32 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase
|
||||
/// re-delivers active alarms once the actor re-enters <c>Connected</c>, so dropping here is safe.
|
||||
/// (<see cref="DriverInstanceActor"/> line ~345: the <c>Reconnecting</c> state's
|
||||
/// <c>Receive<NativeAlarmRaised></c> logs a debug message and discards.)
|
||||
///
|
||||
/// <para>
|
||||
/// To reproduce the race deterministically: a <c>ForceReconnect</c> is enqueued first, then
|
||||
/// the driver fires an alarm while its <c>OnAlarmEvent</c> handler is still attached (the actor
|
||||
/// hasn't yet processed <c>ForceReconnect</c>). The handler's <c>self.Tell(NativeAlarmRaised)</c>
|
||||
/// lands second in the mailbox. The actor then processes <c>ForceReconnect</c> → Reconnecting
|
||||
/// (detaches handler), then processes the queued <c>NativeAlarmRaised</c> in Reconnecting
|
||||
/// → drops it. The default 10 s reconnect interval ensures no retry fires during the check.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Native_alarm_during_reconnect_is_dropped_not_forwarded()
|
||||
{
|
||||
// Long reconnect interval (default 10 s) so the retry doesn't fire during the assertion window.
|
||||
var driver = new AlarmSourceStubDriver();
|
||||
var parent = CreateTestProbe();
|
||||
var actor = parent.ChildActorOf(DriverInstanceActor.Props(
|
||||
driver, reconnectInterval: TimeSpan.FromMilliseconds(50)));
|
||||
var actor = parent.ChildActorOf(DriverInstanceActor.Props(driver));
|
||||
|
||||
// Drive the actor to Connected first, then push it into Reconnecting via DisconnectObserved.
|
||||
// Drive to Connected; confirm the alarm handler is attached.
|
||||
actor.Tell(new DriverInstanceActor.InitializeRequested("{}"));
|
||||
AwaitCondition(() => driver.AlarmSubscriberCount == 1, TimeSpan.FromSeconds(2));
|
||||
|
||||
// One successful alarm-forward from Connected, to confirm the happy path is hot before the test.
|
||||
driver.RaiseAlarm(new AlarmEventArgs(
|
||||
new StubAlarmHandle(),
|
||||
SourceNodeId: "src-node-reconnect",
|
||||
ConditionId: "cond-reconnect",
|
||||
AlarmType: "T",
|
||||
Message: "pre-reconnect raise",
|
||||
Severity: AlarmSeverity.High,
|
||||
SourceTimestampUtc: DateTime.UtcNow,
|
||||
Kind: AlarmTransitionKind.Raise));
|
||||
parent.ExpectMsg<DriverInstanceActor.AttributeAlarmPublished>(TimeSpan.FromSeconds(2));
|
||||
|
||||
// Force Connected → Reconnecting; the alarm handler is detached during this transition.
|
||||
actor.Tell(new DriverInstanceActor.DisconnectObserved("test-induced disconnect"));
|
||||
// Wait until the actor detaches the alarm handler (AlarmSubscriberCount drops to 0).
|
||||
AwaitCondition(() => driver.AlarmSubscriberCount == 0, TimeSpan.FromSeconds(2));
|
||||
|
||||
// Now tell the actor a NativeAlarmRaised (the driver thread can fire this at any time).
|
||||
// In Reconnecting the actor handles NativeAlarmRaised with a debug log and drops it.
|
||||
// Enqueue ForceReconnect FIRST — the actor hasn't processed it yet, so the handler is
|
||||
// still wired. The test thread then immediately fires the alarm on the driver; the handler's
|
||||
// self.Tell(NativeAlarmRaised) lands SECOND in the mailbox.
|
||||
actor.Tell(new DriverInstanceActor.ForceReconnect());
|
||||
driver.RaiseAlarm(new AlarmEventArgs(
|
||||
new StubAlarmHandle(),
|
||||
SourceNodeId: "src-node-reconnect",
|
||||
@@ -140,12 +134,15 @@ public sealed class DriverInstanceActorNativeAlarmTests : RuntimeActorTestBase
|
||||
SourceTimestampUtc: DateTime.UtcNow,
|
||||
Kind: AlarmTransitionKind.Raise));
|
||||
|
||||
// The parent must NOT receive an AttributeAlarmPublished while the actor is Reconnecting.
|
||||
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
// The actor processes: (1) ForceReconnect → Reconnecting (handler detached);
|
||||
// (2) NativeAlarmRaised → dropped (debug log, no forward).
|
||||
// The parent must NOT receive AttributeAlarmPublished from that alarm.
|
||||
// Wait generously — the default reconnect interval of 10 s means no retry fires here.
|
||||
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(500));
|
||||
|
||||
// The actor must still be alive (not crashed/dead-lettered) — a Watch + no Terminated proves it.
|
||||
// The actor must still be alive — Watch + no Terminated (not crashed or dead-lettered).
|
||||
Watch(actor);
|
||||
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(100)); // still no Terminated
|
||||
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(100));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user