fix(r2-01): surface connect-timeout as TimeoutException, not OCE — poll loops no longer die on unreachable-host outages (STAB-14, task 3)

This commit is contained in:
Joseph Doherty
2026-07-13 09:52:42 -04:00
parent 7813304199
commit 4bb25fc039
2 changed files with 22 additions and 1 deletions
@@ -187,6 +187,16 @@ public sealed class S7Driver
cts.CancelAfter(_options.Timeout);
await plc.OpenAsync(cts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException) when (!cancellationToken.IsCancellationRequested)
{
// The linked CancelAfter fired — a connect TIMEOUT, not a caller cancellation.
// Surface it as a TimeoutException so init-time and reconnect-time timeouts report
// the same exception type (STAB-14 consistency); the outer catch still faults health.
try { plc.Dispose(); } catch { }
throw new TimeoutException(
$"S7 connect to {_options.Host}:{_options.Port} timed out after " +
$"{(int)_options.Timeout.TotalMilliseconds} ms.");
}
catch
{
// Dispose the partially-constructed connection so a failed init doesn't leak it
@@ -1205,6 +1215,17 @@ public sealed class S7Driver
cts.CancelAfter(_options.Timeout);
await plc.OpenAsync(cts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException) when (!ct.IsCancellationRequested)
{
// The linked CancelAfter fired — a connect TIMEOUT, not a caller cancellation.
// Surface it as a connection FAILURE (TimeoutException) so callers route it through
// their degrade paths: an escaping OCE reads as teardown and permanently killed the
// subscription poll loops (STAB-14, the unreachable-host regression in the Crit-3 fix).
try { plc.Dispose(); } catch { /* best-effort */ }
throw new TimeoutException(
$"S7 connect to {_options.Host}:{_options.Port} timed out after " +
$"{(int)_options.Timeout.TotalMilliseconds} ms.");
}
catch
{
try { plc.Dispose(); } catch { /* best-effort */ }