test(dcl): de-race MxGateway Unsubscribe stops-routing under load (#288)

This commit is contained in:
Joseph Doherty
2026-06-19 05:01:42 -04:00
parent 84cb8c2abb
commit c72d7b7902
@@ -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<bool> 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<bool> condition, int timeoutMs = 30_000)
{
var sw = System.Diagnostics.Stopwatch.StartNew();
while (!condition() && sw.ElapsedMilliseconds < timeoutMs)