fix(twincat): probe recycle honors cancellation — no uncancellable gate wait (05/STAB-12 compounded part); B6 complete (TwinCAT suite 189 green)

This commit is contained in:
Joseph Doherty
2026-07-13 12:43:06 -04:00
parent 082ee99b6b
commit 502a327f25
3 changed files with 48 additions and 7 deletions
@@ -689,7 +689,7 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
// this the IsConnected fast-path would keep handing back the dead client forever
// (the compounding bug — native pushes would never recover).
if (!success)
await RecycleClientAsync(state).ConfigureAwait(false);
await RecycleClientAsync(state, ct).ConfigureAwait(false);
else
// 05/STAB-16: on a healthy tick, re-register any notifications whose replay
// failed (a transient AddNotification blip) so they recover without a client swap.
@@ -700,8 +700,10 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
{
// Probe threw — the connect-failure path clears the client only when the CONNECT
// failed; a throw from ProbeAsync on an already-connected client would leave it in
// place, so force a recycle to guarantee the next tick rebuilds + replays.
await RecycleClientAsync(state).ConfigureAwait(false);
// place, so force a recycle to guarantee the next tick rebuilds + replays. A shutdown
// racing this recycle cancels the gate wait (STAB-12) — exit the loop cleanly.
try { await RecycleClientAsync(state, ct).ConfigureAwait(false); }
catch (OperationCanceledException) when (ct.IsCancellationRequested) { break; }
}
TransitionDeviceState(state, success ? HostState.Running : HostState.Stopped);
@@ -981,9 +983,12 @@ public sealed class TwinCATDriver : IDriver, IReadable, IWritable, ITagDiscovery
/// liveness, so without this forced recycle a wire-dead-but-port-open client would keep
/// being reused and native pushes would never recover.
/// </summary>
private async Task RecycleClientAsync(DeviceState device)
private async Task RecycleClientAsync(DeviceState device, CancellationToken ct)
{
await device.ConnectGate.WaitAsync(CancellationToken.None).ConfigureAwait(false);
// 05/STAB-12 (compounded part): honor the probe token so a connect wedged under the gate
// (TwinCAT's blocking SDK Connect — the un-fixed root, carried) can't hang the probe loop
// beyond cancellation. Was CancellationToken.None.
await device.ConnectGate.WaitAsync(ct).ConfigureAwait(false);
try
{
if (device.Client is { } c)