fix(s7): bound async wire ops with a wall-clock deadline (R2-01 read leg) #453

Merged
dohertj2 merged 1 commits from fix/r2-01-s7-read-wallclock-timeout into master 2026-07-15 07:12:16 -04:00
Owner

Closes the R2-01 live gate. The S7_1500ConnectTimeoutOutageTests docker-pause blackhole surfaced a real gap offline fakes couldn't: S7.Net's ReadTimeout/WriteTimeout set the TcpClient's synchronous receive/send timeout, which async reads ignore — so a read on an established-but-frozen peer blocked for minutes, wedging the poll loop (no Bad tick, no reconnect). STAB-14 fixed only the connect leg; this is its read-leg sibling.

  • S7OperationDeadline: wall-clock ceiling on every data-plane wire op → TimeoutException on overrun.
  • IsS7ConnectionFatal classifies TimeoutException fatal → mark dead → reopen.
  • Driver.S7.Tests 260/260 (6 new); live gate GREEN (8s recover cycle).
Closes the R2-01 live gate. The `S7_1500ConnectTimeoutOutageTests` docker-pause blackhole surfaced a real gap offline fakes couldn't: S7.Net's `ReadTimeout`/`WriteTimeout` set the TcpClient's *synchronous* receive/send timeout, which async reads ignore — so a read on an established-but-frozen peer blocked for minutes, wedging the poll loop (no Bad tick, no reconnect). STAB-14 fixed only the connect leg; this is its read-leg sibling. - `S7OperationDeadline`: wall-clock ceiling on every data-plane wire op → `TimeoutException` on overrun. - `IsS7ConnectionFatal` classifies `TimeoutException` fatal → mark dead → reopen. - Driver.S7.Tests 260/260 (6 new); live gate GREEN (8s recover cycle).
dohertj2 added 1 commit 2026-07-15 07:12:06 -04:00
fix(s7): bound async wire ops with a wall-clock deadline (R2-01 read leg)
v2-ci / build (pull_request) Successful in 5m5s
v2-ci / unit-tests (pull_request) Failing after 13m50s
88e0977ae6
The R2-01 live gate (S7_1500ConnectTimeoutOutageTests, docker-pause blackhole)
surfaced a real gap the offline fakes couldn't: S7.Net's ReadTimeout/WriteTimeout
map to the TcpClient's ReceiveTimeout/SendTimeout, which govern only SYNCHRONOUS
socket calls — the async read/write paths S7.Net uses ignore them. On an
established-but-frozen peer (frozen PLC / firewall DROP / cable pulled mid-flow,
TCP session still open) a read blocked until the OS TCP stack gave up (minutes),
silently wedging the poll loop: no Bad tick, no reconnect. STAB-14 fixed only the
CONNECT leg (EnsureConnectedAsync CancelAfter); this is its READ-leg sibling.

- New S7OperationDeadline: bounds every data-plane wire op with a wall-clock
  ceiling (= _options.Timeout), surfacing an overrun as TimeoutException. Applied
  in S7PlcAdapter to Read/ReadBytes/Write/WriteBytes/ReadStatus. OpenAsync is left
  to EnsureConnectedAsync's own CancelAfter (not double-bounded).
- IsS7ConnectionFatal now classifies TimeoutException fatal → handle marked dead →
  next EnsureConnectedAsync reopens (connect-timeout fix takes over from there).
- Tests: 5 S7OperationDeadline unit tests (deadline / token-honouring / caller-
  cancel-passthrough / resultless), 1 driver-reaction test (read TimeoutException
  → reopen). Driver.S7.Tests 260/260.
- Live gate S7_1500ConnectTimeoutOutageTests now GREEN against the real snap7 sim
  (8s: baseline Good -> pause blackhole -> Bad tick -> unpause -> recovered Good).
dohertj2 merged commit 4dc937fcb8 into master 2026-07-15 07:12:16 -04:00
Sign in to join this conversation.