fix(driver-twincat): resolve High code-review findings (Driver.TwinCAT-001, -002, -007, -008, -013)
Driver.TwinCAT-001 — InitializeAsync/ReinitializeAsync ignored driverConfigJson. Extracted the DTO-to-options parse into a shared TwinCATDriverFactoryExtensions.ParseOptions; InitializeAsync now re-parses driverConfigJson into a mutable _options field, so a config generation pushed via ReinitializeAsync (added/removed devices, tags, probe settings) is actually applied at runtime. Driver.TwinCAT-002 — LInt/ULInt narrowed to Int32. ToDriverDataType now maps LInt to Int64, ULInt to UInt64, UDInt to UInt32, UInt/USInt to UInt16, Int/SInt to Int16, and the IEC TIME/DATE/DT/TOD types to UInt32 (their raw UDINT counter). Removed the stale "Int64 gap" comment — no truncation or sign flips at the OPC UA encode layer. Driver.TwinCAT-007 — EnsureConnectedAsync was not thread-safe. Connect/reconnect is now serialized per device by a SemaphoreSlim (DeviceState.ConnectGate) with a double-checked connect, mirroring the S7 driver. Concurrent read/write/probe callers can no longer leak a client or race a create-vs-dispose. Driver.TwinCAT-008 — native ADS notification callbacks ran driver logic on the AMS router thread. AdsTwinCATClient now enqueues AdsNotificationEx callbacks onto a bounded Channel drained by a dedicated managed task; the router-thread callback only does a non-blocking TryWrite, so a slow consumer cannot stall ADS notification delivery process-wide. Driver.TwinCAT-013 — TwinCATDriver did not implement IRediscoverable. The driver now implements IRediscoverable; AdsTwinCATClient detects ADS 0x0702 (symbol-version-changed) on read/write paths and raises OnSymbolVersionChanged, which the driver forwards as OnRediscoveryNeeded so Core rebuilds the address space after a PLC program re-download. Adds TwinCATHighFindingsRegressionTests covering all five fixes; updates the data-type mapping assertion in TwinCATDriverTests. TwinCAT driver builds clean; 119 tests pass. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -7,7 +7,7 @@
|
||||
| Review date | 2026-05-22 |
|
||||
| Commit reviewed | `76d35d1` |
|
||||
| Status | Reviewed |
|
||||
| Open findings | 16 |
|
||||
| Open findings | 11 |
|
||||
|
||||
## Checklist coverage
|
||||
|
||||
@@ -36,7 +36,7 @@ a category produced nothing rather than leaving it blank.
|
||||
| Severity | High |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Location | `TwinCATDriver.cs:41-78` |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
|
||||
**Description:** `InitializeAsync` and `ReinitializeAsync` both ignore their `driverConfigJson`
|
||||
parameter entirely. `InitializeAsync` builds device/tag state exclusively from `_options`,
|
||||
@@ -55,7 +55,7 @@ parser) and assign the resulting options to a mutable field, rather than relying
|
||||
constructor-captured `_options`. Alternatively, document explicitly that the constructor is
|
||||
the sole config source and have the Core recreate the driver instance on config change.
|
||||
|
||||
**Resolution:** _(open)_
|
||||
**Resolution:** Resolved 2026-05-22 — extracted the DTO→options parse into a shared TwinCATDriverFactoryExtensions.ParseOptions; InitializeAsync re-parses driverConfigJson into a now-mutable _options field, so ReinitializeAsync applies a changed config generation.
|
||||
|
||||
### Driver.TwinCAT-002
|
||||
|
||||
@@ -64,7 +64,7 @@ the sole config source and have the Core recreate the driver instance on config
|
||||
| Severity | High |
|
||||
| Category | Correctness & logic bugs |
|
||||
| Location | `TwinCATDataType.cs:34-48`, `AdsTwinCATClient.cs:264-281` |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
|
||||
**Description:** `TwinCATDataTypeExtensions.ToDriverDataType` maps `LInt` and `ULInt` (signed/
|
||||
unsigned 64-bit) to `DriverDataType.Int32` (comment: "matches Int64 gap"). The address-space
|
||||
@@ -79,7 +79,7 @@ the range 0x80000000 to 0xFFFFFFFF surface as negative.
|
||||
**Recommendation:** Map `LInt` to `Int64`, `ULInt` to `UInt64`, `UDInt` to `UInt32`, `UInt`
|
||||
to `UInt16`, and `USInt`/`SInt` to their natural widths. Remove the stale "Int64 gap" comment.
|
||||
|
||||
**Resolution:** _(open)_
|
||||
**Resolution:** Resolved 2026-05-22 — ToDriverDataType now maps LInt→Int64, ULInt→UInt64, UDInt→UInt32, UInt/USInt→UInt16, Int/SInt→Int16, and the IEC time types→UInt32; removed the stale Int64-gap comment.
|
||||
|
||||
### Driver.TwinCAT-003
|
||||
|
||||
@@ -178,7 +178,7 @@ fallback. Prefer the first device HostAddress only when one exists (already done
|
||||
| Severity | High |
|
||||
| Category | Concurrency & thread safety |
|
||||
| Location | `TwinCATDriver.cs:413-429` |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
|
||||
**Description:** `EnsureConnectedAsync` is not thread-safe. `ReadAsync`, `WriteAsync`,
|
||||
`SubscribeAsync`, and the per-device `ProbeLoopAsync` background task can all call it
|
||||
@@ -196,7 +196,7 @@ continuously, so this race is not hypothetical under any concurrent read/write l
|
||||
serialize device access with a `SemaphoreSlim` — follow that pattern. Note this also
|
||||
serializes the wire, which `docs/v2/driver-specs.md` recommends for single-connection-per-PLC.
|
||||
|
||||
**Resolution:** _(open)_
|
||||
**Resolution:** Resolved 2026-05-22 — EnsureConnectedAsync is now serialized per device by a SemaphoreSlim (DeviceState.ConnectGate) with a double-checked connect, mirroring the S7 driver; no client is leaked and no disposal race remains.
|
||||
|
||||
### Driver.TwinCAT-008
|
||||
|
||||
@@ -205,7 +205,7 @@ serializes the wire, which `docs/v2/driver-specs.md` recommends for single-conne
|
||||
| Severity | High |
|
||||
| Category | Concurrency & thread safety |
|
||||
| Location | `AdsTwinCATClient.cs:162-169`, `TwinCATDriver.cs:319-324` |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
|
||||
**Description:** Native ADS notification callbacks (`OnAdsNotificationEx`) run on the
|
||||
`AdsClient` AMS router thread. `docs/v2/driver-specs.md` section 6 explicitly calls this out
|
||||
@@ -221,7 +221,7 @@ device in the process.
|
||||
a dedicated managed task before invoking `OnChange`/`OnDataChange`, exactly as the Galaxy
|
||||
`EventPump` does. Keep the router-thread callback to a non-blocking enqueue only.
|
||||
|
||||
**Resolution:** _(open)_
|
||||
**Resolution:** Resolved 2026-05-22 — AdsTwinCATClient now enqueues native AdsNotificationEx callbacks onto a bounded Channel drained by a dedicated managed task; the AMS router thread only does a non-blocking TryWrite, so a slow consumer cannot stall ADS delivery.
|
||||
|
||||
### Driver.TwinCAT-009
|
||||
|
||||
@@ -334,7 +334,7 @@ stream-and-discard design is intentional, report the real footprint of `_nativeS
|
||||
| Severity | High |
|
||||
| Category | Design-document adherence |
|
||||
| Location | `TwinCATDriver.cs:11-12` (capability list), whole file |
|
||||
| Status | Open |
|
||||
| Status | Resolved |
|
||||
|
||||
**Description:** `TwinCATDriver` does not implement `IRediscoverable`. Both
|
||||
`docs/v2/driver-specs.md` section 6 and `docs/v2/driver-stability.md` section "TwinCAT — Deep
|
||||
@@ -352,7 +352,7 @@ on read/write/notification paths, raise `OnRediscoveryNeeded` with a scoped reas
|
||||
re-establish native notifications after the Core re-runs `DiscoverAsync`. This is explicitly
|
||||
part of the documented driver contract, not optional.
|
||||
|
||||
**Resolution:** _(open)_
|
||||
**Resolution:** Resolved 2026-05-22 — TwinCATDriver implements IRediscoverable; AdsTwinCATClient detects ADS 0x0702 on read/write paths and raises OnSymbolVersionChanged, which the driver forwards as OnRediscoveryNeeded so Core rebuilds the address space.
|
||||
|
||||
### Driver.TwinCAT-014
|
||||
|
||||
|
||||
Reference in New Issue
Block a user