From f401a9ea0e736e0e9208105ab3aee9c90b7fd868 Mon Sep 17 00:00:00 2001 From: Joseph Doherty Date: Thu, 28 May 2026 12:51:45 -0400 Subject: [PATCH] fix(comm+site): route BrowseOpcUaNodeCommand via DeploymentManagerActor singleton --- .../Actors/SiteCommunicationActor.cs | 23 ++++++------------- .../Actors/DeploymentManagerActor.cs | 10 ++++++++ 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs index d83beabf..57c8ee1e 100644 --- a/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs +++ b/src/ZB.MOM.WW.ScadaBridge.Communication/Actors/SiteCommunicationActor.cs @@ -146,22 +146,13 @@ public class SiteCommunicationActor : ReceiveActor, IWithTimers Receive(msg => _deploymentManagerProxy.Forward(msg)); // OPC UA Tag Browser (interactive design-time query) — forward to the - // site-local Data Connection Manager actor, which owns the in-memory - // map of live data-connection adapters keyed by ConnectionName and - // executes the browse against the appropriate OPC UA client. The - // manager is not a child of DeploymentManagerActor, so we route via - // ActorSelection rather than the _deploymentManagerProxy field; the - // path matches the registration in AkkaHostedService. ActorSelection - // has no Forward() helper, so we Tell with the original Sender so the - // BrowseOpcUaNodeResult routes straight back to the central UI's Ask - // (via the CentralCommunicationActor sender chain), not to us. - Receive(msg => - { - _log.Debug( - "Routing BrowseOpcUaNodeCommand for connection '{0}' to DataConnectionManager", - msg.ConnectionName); - Context.ActorSelection("/user/dcl-manager").Tell(msg, Sender); - }); + // Deployment Manager singleton, which always lands on the active site + // node. Routing to the site-local /user/dcl-manager directly is wrong + // because the standby node has a dcl-manager too, but its + // DataConnectionActor children (which own the live OPC UA sessions) + // only exist on the singleton's node. The singleton then re-forwards + // to its own /user/dcl-manager, which DOES have the connection. + Receive(msg => _deploymentManagerProxy.Forward(msg)); // Pattern 7: Remote Queries Receive(msg => diff --git a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs index 65721653..6cd65aa3 100644 --- a/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs +++ b/src/ZB.MOM.WW.ScadaBridge.SiteRuntime/Actors/DeploymentManagerActor.cs @@ -6,6 +6,7 @@ using ZB.MOM.WW.ScadaBridge.Commons.Messages.Deployment; using ZB.MOM.WW.ScadaBridge.Commons.Messages.InboundApi; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Instance; using ZB.MOM.WW.ScadaBridge.Commons.Messages.Lifecycle; +using ZB.MOM.WW.ScadaBridge.Commons.Messages.Management; using ZB.MOM.WW.ScadaBridge.Commons.Messages.ScriptExecution; using ZB.MOM.WW.ScadaBridge.Commons.Types.Enums; using ZB.MOM.WW.ScadaBridge.HealthMonitoring; @@ -147,6 +148,15 @@ public class DeploymentManagerActor : ReceiveActor, IWithTimers Receive(RouteInboundApiGetAttributes); Receive(RouteInboundApiSetAttributes); + // OPC UA Tag Browser — singleton-only re-forward to local /user/dcl-manager. + // BrowseOpcUaNodeCommand is routed to this singleton (active node) by + // SiteCommunicationActor so the dcl-manager we forward to is guaranteed + // to be the one holding the live DataConnectionActor children. ActorSelection + // has no Forward() extension in this Akka.NET version, so we Tell with the + // original Sender preserved (semantically identical to Forward). + Receive(msg => + Context.ActorSelection("/user/dcl-manager").Tell(msg, Sender)); + // Internal startup messages Receive(HandleStartupConfigsLoaded); Receive(HandleSharedScriptsLoaded);