diff --git a/code-reviews/Driver.AbLegacy/findings.md b/code-reviews/Driver.AbLegacy/findings.md index 7e7b6ec..67c92c3 100644 --- a/code-reviews/Driver.AbLegacy/findings.md +++ b/code-reviews/Driver.AbLegacy/findings.md @@ -242,7 +242,7 @@ successful poll so a single failed read does not flap the surface. | Severity | Medium | | Category | Error handling & resilience | | Location | `AbLegacyDriver.cs:41-74` | -| Status | Open | +| Status | Resolved | **Description:** `InitializeAsync` starts probe loops with `Task.Run` inside the try block. If `InitializeAsync` fails - or is re-entered - after some probe loops are @@ -257,7 +257,7 @@ and `CancellationTokenSource`s alive holding libplctag handles. Separately, `ShutdownAsync` (cancel probe CTSs, dispose runtimes, clear dictionaries) before rethrowing, so a failed initialise leaves no live background work. -**Resolution:** _(open)_ +**Resolution:** Resolved 2026-05-22 — `InitializeAsync` catch block now cancels and disposes probe CTSs, calls `DisposeRuntimes`, and clears `_devices`/`_tagsByName` before rethrowing, leaving no orphaned background tasks or handles. ### Driver.AbLegacy-010 diff --git a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs index 17d5759..44a451b 100644 --- a/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs +++ b/src/Drivers/ZB.MOM.WW.OtOpcUa.Driver.AbLegacy/AbLegacyDriver.cs @@ -74,6 +74,20 @@ public sealed class AbLegacyDriver : IDriver, IReadable, IWritable, ITagDiscover catch (Exception ex) { _health = new DriverHealth(DriverState.Faulted, null, ex.Message); + // Tear down any probe loops and cached state that were created before the failure so + // that a caller who catches and abandons (rather than retrying via ReinitializeAsync) + // doesn't leave orphaned background tasks, CancellationTokenSources, and libplctag + // handles alive. Mirrors the body of ShutdownAsync without awaiting the poll engine + // (nothing has been subscribed yet at init time). + foreach (var state in _devices.Values) + { + try { state.ProbeCts?.Cancel(); } catch { } + state.ProbeCts?.Dispose(); + state.ProbeCts = null; + state.DisposeRuntimes(); + } + _devices.Clear(); + _tagsByName.Clear(); throw; } return Task.CompletedTask;