fix(r2-01): probe failures mark the handle dead under the gate so the next tick reopens (STAB-15b, task 9)

This commit is contained in:
Joseph Doherty
2026-07-13 10:03:36 -04:00
parent 33044061c3
commit 8999c722a0
3 changed files with 107 additions and 8 deletions
@@ -1596,17 +1596,39 @@ public sealed class S7Driver
await _gate.WaitAsync(probeCts.Token).ConfigureAwait(false);
try
{
// EnsureConnectedAsync doubles as the reconnect backstop: if a read/write
// marked the handle dead (or the socket dropped between ticks), the probe
// reopens it here so recovery doesn't wait for the next data call.
var plc = await EnsureConnectedAsync(probeCts.Token).ConfigureAwait(false);
await plc.ReadStatusAsync(probeCts.Token).ConfigureAwait(false);
success = true;
try
{
// EnsureConnectedAsync doubles as the reconnect backstop: if a read/write
// marked the handle dead (or the socket dropped between ticks), the probe
// reopens it here so recovery doesn't wait for the next data call.
var plc = await EnsureConnectedAsync(probeCts.Token).ConfigureAwait(false);
await plc.ReadStatusAsync(probeCts.Token).ConfigureAwait(false);
success = true;
}
catch (OperationCanceledException) when (ct.IsCancellationRequested)
{
// Loop teardown — rethrow to the outer filter (after finally releases the gate).
throw;
}
catch (OperationCanceledException)
{
// Probe deadline fired mid-PDU: the CPU-status response may still be in
// flight, so the handle can't be reused (half-read ⇒ desync). We hold _gate
// here, keeping _plcDead gate-guarded (STAB-15b).
_plcDead = true;
}
catch (Exception ex)
{
// STAB-15b: classify + mark while holding _gate so a wire-dead/desynced
// handle whose TcpClient still claims Connected is reopened by the NEXT
// tick's EnsureConnectedAsync instead of being re-probed forever.
MarkConnectionDeadIfFatal(ex);
}
}
finally { _gate.Release(); }
}
catch (OperationCanceledException) when (ct.IsCancellationRequested) { return; }
catch { /* transport/timeout/exception — treated as Stopped below */ }
catch { /* gate-wait timeout / other — treated as Stopped below (gate contention is not a connection fault) */ }
TransitionTo(success ? HostState.Running : HostState.Stopped);