diff --git a/tests/ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests/Adapters/MxGatewayDataConnectionTests.cs b/tests/ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests/Adapters/MxGatewayDataConnectionTests.cs index a6548105..b8db9d87 100644 --- a/tests/ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests/Adapters/MxGatewayDataConnectionTests.cs +++ b/tests/ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests/Adapters/MxGatewayDataConnectionTests.cs @@ -5,6 +5,12 @@ using ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Adapters; namespace ZB.MOM.WW.ScadaBridge.DataConnectionLayer.Tests.Adapters; +// Non-parallel with the rest of the DataConnectionManagerActor collection: these adapter +// tests pump a fire-and-forget event loop (ConnectAsync → Task.Run(RunEventLoopAsync)) whose +// thread-pool continuation must run before the WaitUntil attach-barrier observes +// fake.OnUpdate. Serializing within the assembly trims in-assembly thread-pool contention so +// that barrier (and the negative Unsubscribe assertion that follows it) is not starved. +[Collection("DataConnectionManagerActor")] public class MxGatewayDataConnectionTests { private static MxGatewayDataConnection NewAdapter(FakeMxGatewayClient fake) => @@ -249,7 +255,17 @@ public class MxGatewayDataConnectionTests () => adapter.BrowseChildrenAsync(null)); } - private static async Task WaitUntil(Func condition, int timeoutMs = 2000) + // Generous ceiling (30 s, matching the DataConnectionManagerActorCollection rationale): + // every caller polls a *monotonic, positive* barrier — fake.OnUpdate becoming non-null + // (the fire-and-forget event-loop task has attached) or a Disconnected count reaching its + // target. Both only ever transition once and never reset, so a longer ceiling merely + // tolerates thread-pool starvation under full-solution CPU oversubscription; it cannot + // produce a false pass. In Unsubscribe_stops_routing_updates this barrier establishes the + // happens-before that the loop is attached BEFORE the test removes the subscription; the + // negative assertion (hits == 0) runs afterwards and is independent of this timeout — + // the update is delivered synchronously by the test thread, so widening here neither + // weakens nor races the negative check. + private static async Task WaitUntil(Func condition, int timeoutMs = 30_000) { var sw = System.Diagnostics.Stopwatch.StartNew(); while (!condition() && sw.ElapsedMilliseconds < timeoutMs)