Stop OPC UA Read requests from serving stale Good-quality cached values while a Galaxy runtime host is Stopped, and defer probe-transition callbacks through a dispatch-thread queue so MarkHostVariablesBadQuality can no longer deadlock against worker threads waiting on the MxAccess STA thread
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -318,6 +318,86 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
|
||||
sut.HandleProbeUpdate("DevAppEngine.ScanState", Vtq.Good(true)).ShouldBeFalse();
|
||||
}
|
||||
|
||||
// ---------- IsHostStopped (Read-path short-circuit support) ----------
|
||||
|
||||
[Fact]
|
||||
public async Task IsHostStopped_UnknownHost_ReturnsFalse()
|
||||
{
|
||||
var (client, stopSpy, runSpy) = NewSpyHarness();
|
||||
using var sut = Sut(client, 15, stopSpy, runSpy);
|
||||
await sut.SyncAsync(new[] { Engine(20, "DevAppEngine") });
|
||||
|
||||
// Never delivered a callback — state is Unknown. Read-path should NOT short-circuit
|
||||
// on Unknown because the host might come online any moment.
|
||||
sut.IsHostStopped(20).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsHostStopped_RunningHost_ReturnsFalse()
|
||||
{
|
||||
var (client, stopSpy, runSpy) = NewSpyHarness();
|
||||
using var sut = Sut(client, 15, stopSpy, runSpy);
|
||||
await sut.SyncAsync(new[] { Engine(20, "DevAppEngine") });
|
||||
sut.HandleProbeUpdate("DevAppEngine.ScanState", Vtq.Good(true));
|
||||
|
||||
sut.IsHostStopped(20).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsHostStopped_StoppedHost_ReturnsTrue()
|
||||
{
|
||||
var (client, stopSpy, runSpy) = NewSpyHarness();
|
||||
using var sut = Sut(client, 15, stopSpy, runSpy);
|
||||
await sut.SyncAsync(new[] { Engine(20, "DevAppEngine") });
|
||||
sut.HandleProbeUpdate("DevAppEngine.ScanState", Vtq.Good(true));
|
||||
sut.HandleProbeUpdate("DevAppEngine.ScanState", Vtq.Good(false));
|
||||
|
||||
sut.IsHostStopped(20).ShouldBeTrue();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsHostStopped_AfterRecovery_ReturnsFalse()
|
||||
{
|
||||
var (client, stopSpy, runSpy) = NewSpyHarness();
|
||||
using var sut = Sut(client, 15, stopSpy, runSpy);
|
||||
await sut.SyncAsync(new[] { Engine(20, "DevAppEngine") });
|
||||
sut.HandleProbeUpdate("DevAppEngine.ScanState", Vtq.Good(true));
|
||||
sut.HandleProbeUpdate("DevAppEngine.ScanState", Vtq.Good(false));
|
||||
sut.HandleProbeUpdate("DevAppEngine.ScanState", Vtq.Good(true));
|
||||
|
||||
sut.IsHostStopped(20).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsHostStopped_UnknownGobjectId_ReturnsFalse()
|
||||
{
|
||||
var (client, stopSpy, runSpy) = NewSpyHarness();
|
||||
using var sut = Sut(client, 15, stopSpy, runSpy);
|
||||
await sut.SyncAsync(new[] { Engine(20, "DevAppEngine") });
|
||||
|
||||
// Not a probed host — defensive false rather than throwing.
|
||||
sut.IsHostStopped(99999).ShouldBeFalse();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task IsHostStopped_TransportDisconnected_UsesUnderlyingState()
|
||||
{
|
||||
// Critical contract: IsHostStopped is intended for the Read-path short-circuit and
|
||||
// uses the underlying state directly, NOT the GetSnapshot transport-gated rewrite.
|
||||
// When the transport is disconnected, MxAccess reads will fail via the normal error
|
||||
// path; we don't want IsHostStopped to double-flag the Read as stopped if the host
|
||||
// itself was actually Running before the transport dropped.
|
||||
var (client, stopSpy, runSpy) = NewSpyHarness();
|
||||
using var sut = Sut(client, 15, stopSpy, runSpy);
|
||||
await sut.SyncAsync(new[] { Engine(20, "DevAppEngine") });
|
||||
sut.HandleProbeUpdate("DevAppEngine.ScanState", Vtq.Good(true));
|
||||
|
||||
client.State = ConnectionState.Disconnected;
|
||||
|
||||
// Running state preserved — short-circuit does NOT fire during transport outages.
|
||||
sut.IsHostStopped(20).ShouldBeFalse();
|
||||
}
|
||||
|
||||
// ---------- Callback exception safety ----------
|
||||
|
||||
[Fact]
|
||||
|
||||
Reference in New Issue
Block a user