From 342a825d698f002095d758a747b7d98ace187635 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Mon, 9 Nov 2026 14:17:50 -0500 Subject: [PATCH] ci: concurrent-write test's held write hit the fixed 5 s write cap under CI timefactor A_queued_write_older_than_the_budget_is_failed_BadTimeout_instead_of_written_late failed hosted runs 2215 (7ec5b95d) and 2260 (8ef5adc6) at "(await inFlight).Success should be True but was False", after 7 s each time. It is not load: it fails on an idle machine with AKKA_TEST_TIMEFACTOR=3, the value v2-ci sets, and passes with 1. Akka's AwaitCondition with no interval polls every max/10 of the dilated budget: PresenceBudget 15 s x 3 = 45 s, so a 4.5 s sleep after a first poll that runs before the pool has entered the write. The held write is bounded by DriverInstanceActor.RunWriteAsync's fixed, undilated 5 s CancellationTokenSource, so 4.5 s of polling plus the test's 600 ms settle cancelled it ("write timeout") before ReleaseWrites(). The sibling tests that hold a write took 7 s for the same reason and passed only by margin. All eight AwaitCondition calls in the class now go through AwaitWritesEntered, which polls every 20 ms. The budget (PresenceBudget) and every assertion are unchanged. Local, AKKA_TEST_TIMEFACTOR=3: before 6/7 (this test failing, 7 s); after 7/7 in three runs, test 3 s. Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV --- ...DriverInstanceActorConcurrentWriteTests.cs | 28 +++++++++++++------ 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConcurrentWriteTests.cs b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConcurrentWriteTests.cs index 484b2b10..588bb431 100644 --- a/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConcurrentWriteTests.cs +++ b/tests/Server/ZB.MOM.WW.OtOpcUa.Runtime.Tests/Drivers/DriverInstanceActorConcurrentWriteTests.cs @@ -22,6 +22,18 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa { private static readonly TimeSpan WriteLatency = TimeSpan.FromMilliseconds(1200); + /// + /// Polls for writes inside the driver with an EXPLICIT short interval. Akka's + /// AwaitCondition without one polls every max / 10 of the DILATED budget — 1.5 s locally, 4.5 s + /// under CI's AKKA_TEST_TIMEFACTOR=3. The write under test is already held inside the driver by then, + /// and DriverInstanceActor.RunWriteAsync bounds it with a fixed (undilated) 5 s token, so one missed + /// first poll plus the test's own settle window cancelled it ("write timeout") before + /// ReleaseWrites(): A_queued_write_older_than_the_budget… failed every CI run that hit it + /// (hosted runs 2215 and 2260), and fails locally at timefactor 3. + /// + private void AwaitWritesEntered(SlowWritableDriver driver, int count) => + AwaitCondition(() => driver.WritesEntered == count, PresenceBudget, TimeSpan.FromMilliseconds(20)); + private (IActorRef Actor, Akka.TestKit.TestProbe Parent) Connect( IDriver driver, int? maxWritesInFlight = null, TimeSpan? queuedWriteBudget = null, TimeSpan? writeTimeout = null) { @@ -67,7 +79,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var pending = actor.Ask( new DriverInstanceActor.WriteAttribute("tag-a", 7), PresenceBudget); - AwaitCondition(() => driver.WritesEntered == 1, PresenceBudget); + AwaitWritesEntered(driver, 1); driver.FireDataChange("tag-a", 3.5); var published = parent.ExpectMsg(PresenceBudget); @@ -90,7 +102,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa new DriverInstanceActor.WriteAttribute($"tag-{i}", i), PresenceBudget)) .ToArray(); - AwaitCondition(() => driver.WritesEntered == 2, PresenceBudget); + AwaitWritesEntered(driver, 2); // Settle: a third write entering the driver would show up here. await Task.Delay(300); driver.WritesEntered.ShouldBe(2, "the cap let a third write into the driver"); @@ -119,13 +131,13 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var first = actor.Ask( new DriverInstanceActor.WriteAttribute("tag-a", 1), PresenceBudget); - AwaitCondition(() => driver.WritesEntered == 1, PresenceBudget); + AwaitWritesEntered(driver, 1); var second = actor.Ask( new DriverInstanceActor.WriteAttribute("tag-a", 2), PresenceBudget); // Positive control: a DIFFERENT tag is not held behind tag-a. var other = actor.Ask( new DriverInstanceActor.WriteAttribute("tag-b", 9), PresenceBudget); - AwaitCondition(() => driver.WritesEntered == 2, PresenceBudget); + AwaitWritesEntered(driver, 2); driver.EnteredRefs.ShouldBe(new[] { "tag-a", "tag-b" }); await Task.Delay(300); @@ -149,7 +161,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var inFlight = actor.Ask( new DriverInstanceActor.WriteAttribute("tag-0", 0), PresenceBudget); - AwaitCondition(() => driver.WritesEntered == 1, PresenceBudget); + AwaitWritesEntered(driver, 1); var stale = actor.Ask( new DriverInstanceActor.WriteAttribute("tag-1", 1), PresenceBudget); @@ -181,7 +193,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var hung = actor.Ask( new DriverInstanceActor.WriteAttribute("tag-a", 1), PresenceBudget); - AwaitCondition(() => driver.WritesEntered == 1, PresenceBudget); + AwaitWritesEntered(driver, 1); var hungResult = await hung; hungResult.Success.ShouldBeFalse(); @@ -193,7 +205,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa // abandoned one is still inside it. var later = actor.Ask( new DriverInstanceActor.WriteAttribute("tag-a", 2), PresenceBudget); - AwaitCondition(() => driver.WritesEntered == 2, PresenceBudget); + AwaitWritesEntered(driver, 2); driver.EnteredRefs.ShouldBe(new[] { "tag-a", "tag-a" }); var laterResult = await later; laterResult.Success.ShouldBeTrue(laterResult.Reason); @@ -209,7 +221,7 @@ public sealed class DriverInstanceActorConcurrentWriteTests : RuntimeActorTestBa var inFlight = actor.Ask( new DriverInstanceActor.WriteAttribute("tag-0", 0), PresenceBudget); - AwaitCondition(() => driver.WritesEntered == 1, PresenceBudget); + AwaitWritesEntered(driver, 1); var queued = actor.Ask( new DriverInstanceActor.WriteAttribute("tag-1", 1), PresenceBudget);