I-1: a PUT/GET-disabled CPU (every read denied) stayed Faulted with
LastSuccessfulRead frozen, so the comms-loss watchdog reconnected and
re-subscribed it every 90 s forever. Both access-denied arms now also record
an answer: Faulted is kept (worst state wins), the stamp advances, and the
sticky write fault still clears only on a real success. Pinned end to end in
Runtime.Tests: the real S7Driver under the real DriverInstanceActor watchdog
is not reconnected across 2x the threshold; the control (every read timing
out) is.
Minors:
- m-1: injectable pass clock (UtcNow); "LastSuccessfulRead advanced" is now
asserted strictly, including the write-refusal case.
- m-2: the editor uses S7TagShapeRules.IsByteAnchored instead of its own list.
- m-3: a release that races an init publish also cancels the probe loop the
publish started after the release took its snapshot.
- m-4: a retained poll failing during a lifecycle operation writes no health.
- m-5: a multi-var chunk that fell back memoises its members refused alone.
- m-7: access-denied needs S7.Net's exact wording and no transport fault in
the chain.
- m-8: sync teardown clears the write-side Degraded owner; a sync Dispose
racing a running shutdown is tested.
Refs #742
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT
Overlapping InitializeAsync calls (the actor's fire-and-forget init plus a
retry tick) could leak an opened PLC connection and orphan a probe loop: no
gate guarded the lifecycle, only _gate for I/O.
- _lifecycleGate (the Modbus pattern) serialises Initialize / Reinitialize /
Shutdown / Dispose; teardown waits a bounded time, then tears down anyway.
- Every initialize and teardown starts a lifecycle generation. Health writes
(init, read/write passes, poll errors, shutdown) and the init publish carry
it and are dropped once it has moved on, so an abandoned init never
publishes over a later shutdown and a shutdown's final write never clobbers
a newer init. A superseded init disposes what it opened and throws.
- The init publish (options, tag tables, Plc, probe loop) happens under _gate;
the tag tables are built fresh and swapped by one reference write instead of
being cleared in place under live poll passes.
- Read/write passes re-check initialization under _gate, so a pass that waited
out a teardown cannot reopen a connection on a shut-down driver.
- The semaphores are no longer disposed: Dispose can race a running shutdown.
Refs #742
Claude-Session: https://claude.ai/code/session_01Ub9hGmYLQvn9XETzw7CMsT