fix(otopcua): cancel pending rediscover timer on TriggerRediscovery + test hardening (follow-up C)
This commit is contained in:
+40
-20
@@ -302,28 +302,31 @@ public sealed class DriverInstanceActorDiscoveryTests : RuntimeActorTestBase
|
||||
[Fact]
|
||||
public void TriggerRediscovery_when_Connected_reruns_discovery()
|
||||
{
|
||||
var driver = new DiscoverableStubDriver();
|
||||
// Once-policy growing stub: exactly ONE pass per (re)kick, so each StartDiscovery publishes precisely
|
||||
// one DiscoveredNodesReady — the trigger's effect is asserted with a single ExpectMsg + ExpectNoMsg
|
||||
// (no second settling pass to drain, and no stale-tick double pass alongside the fresh one).
|
||||
var driver = new GrowingDiscoverableStubDriver(DiscoveryRediscoverPolicy.Once);
|
||||
var parent = CreateTestProbe();
|
||||
var actor = parent.ChildActorOf(DriverInstanceActor.Props(
|
||||
driver, rediscoverInterval: TimeSpan.FromMilliseconds(20)));
|
||||
|
||||
actor.Tell(new DriverInstanceActor.InitializeRequested("{}"));
|
||||
|
||||
// Let the initial post-connect loop settle (passes 0,0,3,3) and confirm it stopped.
|
||||
for (var i = 0; i < 4; i++)
|
||||
parent.ExpectMsg<DriverInstanceActor.DiscoveredNodesReady>(TimeSpan.FromSeconds(2));
|
||||
// Initial connect: Once ⇒ exactly one pass (growing set → 1 node), then it settles.
|
||||
parent.ExpectMsg<DriverInstanceActor.DiscoveredNodesReady>(TimeSpan.FromSeconds(2)).Nodes.Count.ShouldBe(1);
|
||||
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
|
||||
var passesBeforeTrigger = driver.DiscoverCount; // 4
|
||||
var passesBeforeTrigger = driver.DiscoverCount; // 1
|
||||
|
||||
// Re-kick discovery via the new message — the cache is warm, so the fresh pass sees the 3-node set.
|
||||
// Re-kick discovery via the new message — Once ⇒ exactly one fresh pass (growing set → 2 nodes).
|
||||
actor.Tell(new DriverInstanceActor.TriggerRediscovery());
|
||||
|
||||
var afterTrigger = parent.ExpectMsg<DriverInstanceActor.DiscoveredNodesReady>(TimeSpan.FromSeconds(2));
|
||||
afterTrigger.Nodes.Count.ShouldBe(3);
|
||||
afterTrigger.Nodes.Count.ShouldBe(2);
|
||||
afterTrigger.DriverInstanceId.ShouldBe(driver.DriverInstanceId);
|
||||
|
||||
// A fresh pass genuinely ran — DiscoverCount advanced past the settled count.
|
||||
driver.DiscoverCount.ShouldBeGreaterThan(passesBeforeTrigger);
|
||||
// Exactly one fresh pass ran — DiscoverCount advanced by one and no extra pass arrived.
|
||||
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
driver.DiscoverCount.ShouldBe(passesBeforeTrigger + 1);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -351,32 +354,49 @@ public sealed class DriverInstanceActorDiscoveryTests : RuntimeActorTestBase
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="DriverInstanceActor.TriggerRediscovery"/> received while NOT Connected (still Connecting,
|
||||
/// before init completes) is a clean silent no-op: no discovery pass runs, nothing is published, and
|
||||
/// the actor neither crashes nor dies (the driver's eventual reconnect re-discovers anyway). A
|
||||
/// follow-up connect then discovers normally, proving the actor is unharmed.
|
||||
/// <see cref="DriverInstanceActor.TriggerRediscovery"/> received while NOT Connected is a clean silent
|
||||
/// no-op in EVERY non-Connected state: no discovery pass runs, nothing is published, and the actor
|
||||
/// neither crashes nor dies (its eventual (re)connect re-discovers anyway). Covers both <c>Connecting</c>
|
||||
/// (before init completes) and <c>Reconnecting</c> (after a <see cref="DriverInstanceActor.ForceReconnect"/>,
|
||||
/// parked there by a long reconnect interval), with an intervening connect proving the actor is unharmed.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void TriggerRediscovery_when_not_Connected_is_a_silent_noop()
|
||||
{
|
||||
var driver = new DiscoverableStubDriver();
|
||||
// Once-growing stub so a successful connect publishes exactly one pass (clean confirmation of state);
|
||||
// a long reconnect interval so the actor parks in Reconnecting deterministically within the test window.
|
||||
var driver = new GrowingDiscoverableStubDriver(DiscoveryRediscoverPolicy.Once);
|
||||
var parent = CreateTestProbe();
|
||||
var actor = parent.ChildActorOf(DriverInstanceActor.Props(
|
||||
driver, rediscoverInterval: TimeSpan.FromMilliseconds(20)));
|
||||
driver,
|
||||
reconnectInterval: TimeSpan.FromSeconds(30),
|
||||
rediscoverInterval: TimeSpan.FromMilliseconds(20)));
|
||||
Watch(actor);
|
||||
|
||||
// The actor boots into Connecting; send the trigger BEFORE InitializeRequested so it is handled
|
||||
// in a non-Connected state.
|
||||
// (1) Connecting: the actor boots into Connecting; send the trigger BEFORE InitializeRequested so it
|
||||
// is handled in that non-Connected state.
|
||||
actor.Tell(new DriverInstanceActor.TriggerRediscovery());
|
||||
|
||||
// No discovery resulted, and the actor is unharmed (no Terminated arrives at the watching test actor).
|
||||
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(100));
|
||||
driver.DiscoverCount.ShouldBe(0);
|
||||
|
||||
// Sanity: the actor still works — driving it to Connected discovers normally afterwards.
|
||||
// Drive to Connected (proves the Connecting-state trigger left the actor working); Once ⇒ one pass.
|
||||
actor.Tell(new DriverInstanceActor.InitializeRequested("{}"));
|
||||
parent.ExpectMsg<DriverInstanceActor.DiscoveredNodesReady>(TimeSpan.FromSeconds(2));
|
||||
parent.ExpectMsg<DriverInstanceActor.DiscoveredNodesReady>(TimeSpan.FromSeconds(2)).Nodes.Count.ShouldBe(1);
|
||||
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(200));
|
||||
var passesAfterConnect = driver.DiscoverCount; // 1
|
||||
|
||||
// (2) Reconnecting: ForceReconnect parks the actor in Reconnecting (30s retry interval ⇒ no auto
|
||||
// reconnect within the window). A TriggerRediscovery here must ALSO be a clean silent no-op. Both
|
||||
// messages are processed in order, so the trigger is handled while Reconnecting.
|
||||
actor.Tell(new DriverInstanceActor.ForceReconnect());
|
||||
actor.Tell(new DriverInstanceActor.TriggerRediscovery());
|
||||
|
||||
parent.ExpectNoMsg(TimeSpan.FromMilliseconds(300));
|
||||
ExpectNoMsg(TimeSpan.FromMilliseconds(100)); // still alive — no Terminated
|
||||
driver.DiscoverCount.ShouldBe(passesAfterConnect); // no fresh pass while Reconnecting
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user