fix(drivers): #630 review - AbCip disposes alarm projection + clears tag tables, join not abortable by init token; S7 swaps Plc under _gate; doc rationale

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-08 14:39:05 -04:00
parent 461133235a
commit 18a20981a5
6 changed files with 56 additions and 10 deletions
@@ -226,7 +226,7 @@ public sealed class S7Driver
// #630: InitializeAsync is reachable on a live driver (DriverInstanceActor.AdoptConfigDuringInit
// runs it with no teardown first). Stop the previous probe loop and drop the previous connection
// before they are overwritten below. Deliberately NOT ShutdownAsync: that also drops _poll and the
// subscriptions, which nothing would re-drive.
// subscriptions, which are owned by the shutdown path (the actor re-subscribes after every successful init).
await ReleaseConnectionAsync().ConfigureAwait(false);
Health = new DriverHealth(DriverState.Initializing, null, null);
try
@@ -395,10 +395,22 @@ public sealed class S7Driver
_probeCts = null;
_probeTask = null;
_initialized = false;
_plcDead = false;
try { Plc?.Dispose(); } catch { /* best-effort — tearing down anyway */ }
Plc = null;
// Swap the connection out under _gate so a Read/Write/poll pass that already holds the gate (they
// re-read Plc inside it) can never see a disposed Plc mid-swap. Bounded: a wedged holder must not hang
// teardown, in which case we proceed exactly as the un-gated path always did.
var gated = false;
try { gated = await _gate.WaitAsync(DrainTimeout).ConfigureAwait(false); } catch { }
try
{
_initialized = false;
_plcDead = false;
try { Plc?.Dispose(); } catch { /* best-effort — tearing down anyway */ }
Plc = null;
}
finally
{
if (gated) { try { _gate.Release(); } catch (ObjectDisposedException) { } }
}
}
/// <inheritdoc />