fix(dcl): push tag-resolution counts on first subscribe (register row 55)
A site health report showed tagResolutionCounts 0/0 for a connection whose dataConnectionTagQuality on the SAME report read good:2 — reproduced live on the docker rig against site-a's rig-probe-1 instance (2 Good OPC UA tags on "OPC PLC Simulator"), on the ACTIVE node's own report. Not a standby-shadowing or keying/removal problem, and not a serialization problem: both dictionaries live on one collector instance on one node, and the CentralControlDtoMapper round-trip is correct in both directions. The actor's own derived counts (TotalSubscribedTagCount / ResolvedTagCount) were right the whole time — GetHealthReport replied 2/2. Root cause: HandleSubscribeCompleted — the path that registers tags on a first, healthy subscribe — mutated _instancesByTag and _subscriptionIds but never pushed to ISiteHealthCollector. The only pushes were BecomeConnected's (fires before any instance subscribes, so its 0/0 is legitimate), the batch apply (reconnect re-subscribe / resolution probe) and unsubscribe (teardown). A connection whose tags all resolve first time reaches none of those, and ScheduleTagResolutionRetry cancels the retry timer outright when nothing is unresolved — so the connect-time 0/0 was never superseded and stood forever. The blind spot was therefore worst on a perfectly healthy connection, which is exactly when the "device still booting / tag path unresolved" diagnostic should read clean rather than empty. Any reconnect masked it (the batch path pushes), which is why it presented only on a clean first deploy. Fix: push the derived counts at the end of the first-subscribe apply, mirroring the existing push site in HandleBatchSubscribeCompleted. No semantic change to the null/no-data convention — the map entry already existed, its values were simply stale zeros. Tests: fail-before regression test pins the exact mechanism (NSubstitute records a single UpdateTagResolution(name, 0, 0) pre-fix while the actor reports 2/2). DCL 320, HealthMonitoring 97, SiteRuntime 605, Communication 717 all green; slnx builds clean. Docs: Component-DataConnectionLayer.md said the counts were "derived at report time", which implies a pull that cannot go stale and papered over this bug. Corrected to state they are pushed, name the four push sites, and record why a healthy connection was the worst case.
This commit is contained in:
@@ -1275,6 +1275,20 @@ public class DataConnectionActor : UntypedActor, IWithStash, IWithTimers
|
||||
// Fire-and-forget release of every handle this request created redundantly.
|
||||
_ = UnsubscribeIdsAsync(_adapter, redundantIds);
|
||||
|
||||
// Publish the resolution counts this request just changed. Without this push a
|
||||
// connection whose tags ALL resolve on the first attempt reported 0/0 forever:
|
||||
// the only other pushes are BecomeConnected's (which fires before any instance
|
||||
// has subscribed, so its 0/0 is legitimate but is never superseded),
|
||||
// HandleBatchSubscribeCompleted's (reconnect re-subscribe / resolution probe) and
|
||||
// HandleUnsubscribe's (teardown) — and ScheduleTagResolutionRetry below cancels
|
||||
// the retry timer outright when nothing is unresolved, so no later tick pushed
|
||||
// either. The blind spot was therefore worst on a perfectly healthy connection,
|
||||
// which is precisely when the "device still booting / tag path unresolved"
|
||||
// diagnostic is supposed to read clean rather than empty. Mirrors the push site
|
||||
// in HandleBatchSubscribeCompleted, and stays on the actor thread where both
|
||||
// derived counts are stable.
|
||||
_healthCollector.UpdateTagResolution(_connectionName, TotalSubscribedTagCount, ResolvedTagCount);
|
||||
|
||||
// Now that every tag is registered in
|
||||
// _subscriptionsByInstance, deliver the values captured by the initial read.
|
||||
// Re-entering via Self reuses HandleTagValueReceived's generation guard, fan-out
|
||||
|
||||
Reference in New Issue
Block a user