fix(s7): connect backoff, worst-state write health, Volatile health, array writes (#602, #610)

STAB-21 ConnectionBackoff in EnsureConnectedAsync; STAB-22 write-pass accumulator with a
write-side Faulted that a clean read does not erase; _health on the Volatile idiom;
S7-016 contiguous array write (Bool arrays stay refused).

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 06:59:29 -04:00
parent 439752d3b8
commit 82372f1672
8 changed files with 769 additions and 70 deletions
@@ -149,6 +149,8 @@ public sealed class S7DriverReconnectTests
{
var factory = new FakeS7PlcFactory();
using var drv = new S7Driver(Options(), "s7-reopen-fail", factory);
var clock = new DateTime(2026, 1, 1, 0, 0, 0, DateTimeKind.Utc);
drv.BackoffClock = () => clock;
await drv.InitializeAsync("{}", TestContext.Current.CancellationToken);
// Drop the live socket, and make the NEXT-created connection fail to open.
@@ -161,7 +163,9 @@ public sealed class S7DriverReconnectTests
down[0].StatusCode.ShouldNotBe(0u);
drv.GetHealth().State.ShouldBe(DriverState.Degraded);
// PLC comes back; the next call reopens cleanly and the tag reads Good.
// PLC comes back, and the 1 s STAB-21 backoff window the failed reopen opened has elapsed;
// the next call reopens cleanly and the tag reads Good.
clock += TimeSpan.FromSeconds(2);
var up = await drv.ReadAsync(["W0"], TestContext.Current.CancellationToken);
up[0].StatusCode.ShouldBe(0u);
drv.GetHealth().State.ShouldBe(DriverState.Healthy);