fix(twincat): replay native ADS notifications after reconnect (archreview STAB-2 / Critical 4)
Native ADS notifications (the default subscribe mode) were stored as opaque handles with no record of the symbol/type/interval/handler needed to replay. On a client swap (EnsureConnectedAsync building a fresh client after a drop) the notifications were silently orphaned — no Bad status, no error, pushes just stopped until redeploy. Compounding: the IsConnected fast-path keys on AMS-port state, not wire liveness, and a probe failure only transitioned state without recycling the dead client. Fix: - Store REPLAYABLE INTENT: NativeRegistration (symbol/type/bit/interval/onChange + swappable live handle) hung off DeviceState.NativeRegistrations, populated by SubscribeAsync via RegisterNotificationAsync (under ConnectGate). - Split EnsureConnectedAsync into a gate wrapper + EnsureConnectedUnderGateAsync core; when the core installs a NEW client it replays every stored intent onto it and swaps the live handle (disposing the dead one). Register + replay both run under ConnectGate so they can't race. - Probe loop: on a wire-probe failure (false or throw) RecycleClientAsync disposes+nulls the client so the next tick rebuilds + replays — closes the fast-path-keys-on-port-state compounding bug. No TwinCAT docker fixture exists (integration needs a real TC3 XAR), so the fake-client unit tests are the authoritative coverage: - 4 new guards in TwinCATReconnectReplayTests (replay-onto-fresh-client + push reaches OnDataChange + old handle disposed; replay-all-tags; unsubscribe-after-reconnect stops replaying; probe-failure recycles+rebuilds). - Full TwinCAT unit suite 174/174 green; full solution builds 0 errors.
This commit is contained in:
@@ -42,6 +42,11 @@ internal class FakeTwinCATClient : ITwinCATClient
|
||||
/// <summary>Test hook — fire the symbol-version-changed signal as the real client would.</summary>
|
||||
public void FireSymbolVersionChanged() => OnSymbolVersionChanged?.Invoke(this, EventArgs.Empty);
|
||||
|
||||
/// <summary>Test hook — simulate a wire-level drop that leaves the AMS port "connected" but dead:
|
||||
/// flips <see cref="IsConnected"/> to false without disposing, so the driver's IsConnected
|
||||
/// fast-path treats the client as stale and rebuilds (exercises the STAB-2 reconnect+replay).</summary>
|
||||
public void SimulateWireDrop() => IsConnected = false;
|
||||
|
||||
/// <summary>Simulates connecting to the TwinCAT system.</summary>
|
||||
/// <param name="address">The AMS address to connect to.</param>
|
||||
/// <param name="timeout">The connection timeout.</param>
|
||||
|
||||
Reference in New Issue
Block a user