Harden OPC UA layer: reconnect races, handle allocation, interval/security wiring, status semantics - #171
Conversation
Code ReviewThis is a solid hardening PR that closes several real races and wiring gaps. The fixes are well-targeted and the test coverage is good. What's done well
Issues1. Narrow reconnect/disconnect race remainsIn DisposeReconnectCts(); // clears _reconnectCts to null
var cts = new CancellationTokenSource(); // Disconnect() can fire HERE
lock (_reconnectCtsLock) { _reconnectCts = cts; }
var token = cts.Token;If One safe fix: keep the entire create-and-assign sequence inside a single lock acquisition so 2. Potential null dereference in
|
…urity wiring, status semantics - ReconnectAsync now works against a locally captured CancellationTokenSource; the field swap in DisposeReconnectCts happens under a lock, closing the TOCTOU where a concurrent Disconnect could double-dispose or NRE, and a session recreated after the user disconnected is closed instead of living on as a ghost connection. - AddNodeAsync allocates the client handle inside the dedup lock; the unsynchronized increment let two concurrent adds collide on one handle and silently overwrite each other's dictionary entries. - The last-resort subscription restore path now raises VariableRemoved for every lost node so the UI no longer shows rows stuck at (reconnecting...) with dangling handles. - settings.samplingIntervalMs is finally honored: SubscriptionManager gains a SamplingInterval property used for monitored items, ConnectionManager stores both intervals from ConnectAsync and reuses them when subscriptions are restored after reconnect (previously restores fell back to 250 ms). - Config securityMode/securityPolicy and samplingIntervalMs are passed from MainWindow.LoadConfigurationAsync to ConnectAsync, closing the declared loose end from PR #160; a warning is logged when credentials are about to be sent over a SecurityMode=None endpoint. - MonitoredNode treats Good-with-info-bits status codes (e.g. GoodClamped) as Good: severity is the top two bits, not == 0. https://claude.ai/code/session_012Vopnd9vWkzELveHRgZhie
cd25302 to
d4a618b
Compare
Summary
Hardens the OPC UA layer per the v1.0 follow-up review (
docs/V1-REVIEW-FOLLOWUP.md, items 10–12 and the interval/security/status lows):ReconnectAsyncnow works against a locally capturedCancellationTokenSource; the field swap inDisposeReconnectCtshappens under a lock (no more double-dispose/NRE TOCTOU with a concurrentDisconnect), and if the user disconnects while a session recreate is in flight, the freshly created session is closed instead of living on as a ghost connection behind a "Disconnected" UI.AddNodeAsyncallocates_nextClientHandleinside the dedup lock — the unsynchronized++let two concurrent adds of different nodes collide on one handle and silently overwrite each other's entries in the three handle-keyed dictionaries (same TOCTOU class PR Harden SubscriptionManager: dedup race, handler leaks, dead field #158 closed for the NodeId dedup, one line away).VariableRemovedfor every lost node, so the UI no longer shows rows stuck at "(reconnecting...)" with handles the new subscription manager knows nothing about.settings.samplingIntervalMsis finally honored — it was a documented no-op (hardcoded 250 in bothAddNodeAsyncandRecreateSubscriptionsAsync).SubscriptionManagergains a clampedSamplingIntervalproperty;ConnectionManagerstores both intervals fromConnectAsyncand reuses them when subscriptions are restored after reconnect (restores previously fell back to the 250 ms defaults too).MainWindow.LoadConfigurationAsyncnow passessecurityMode/securityPolicy/samplingIntervalMsfrom the config file toConnectAsync. A warning is logged when credentials are about to be sent over aSecurityMode=Noneendpoint (the other declared loose end).MonitoredNodetreats Good-with-info-bits codes (e.g.GoodClamped0x00300000) as Good — severity is the top two bits, not== 0— so such values no longer render as raw hex with non-good row styling.Test plan
dotnet build Opcilloscope.sln -c Release— 0 warnings, 0 errorsdotnet test -c Release— 620/620 passing (7 new tests: Good-substatus semantics, SamplingInterval clamping)samplingIntervalMs: 5000→ server diagnostics show 5000 ms sampling; disconnect during an auto-reconnect → no ghost sessionPart of the v1 follow-up punch list (
docs/V1-REVIEW-FOLLOWUP.md, items 10–12 + lows).https://claude.ai/code/session_012Vopnd9vWkzELveHRgZhie
Generated by Claude Code