fix: route debug stream events through ClusterClient site→central path

ClusterClient Sender refs are temporary proxies — valid for immediate reply
but not durable for future Tells. Events now flow as DebugStreamEvent through
SiteCommunicationActor → ClusterClient → CentralCommunicationActor → bridge
actor (same pattern as health reports). Also fix DebugStreamHub to use
IHubContext for long-lived callbacks instead of transient hub instance.
This commit is contained in:
Joseph Doherty
2026-03-21 11:32:17 -04:00
parent 41aff339b2
commit 3efec91386
7 changed files with 76 additions and 21 deletions

View File

@@ -19,11 +19,16 @@ public class DebugStreamHub : Hub
private const string SessionIdKey = "DebugStreamSessionId";
private readonly DebugStreamService _debugStreamService;
private readonly IHubContext<DebugStreamHub> _hubContext;
private readonly ILogger<DebugStreamHub> _logger;
public DebugStreamHub(DebugStreamService debugStreamService, ILogger<DebugStreamHub> logger)
public DebugStreamHub(
DebugStreamService debugStreamService,
IHubContext<DebugStreamHub> hubContext,
ILogger<DebugStreamHub> logger)
{
_debugStreamService = debugStreamService;
_hubContext = hubContext;
_logger = logger;
}
@@ -105,6 +110,10 @@ public class DebugStreamHub : Hub
try
{
// Use IHubContext for callbacks — the hub instance is transient (disposed after method returns),
// but IHubContext is a singleton that remains valid for the lifetime of the connection.
var hubClients = _hubContext.Clients;
var session = await _debugStreamService.StartStreamAsync(
instanceId,
onEvent: evt =>
@@ -113,17 +122,17 @@ public class DebugStreamHub : Hub
_ = evt switch
{
AttributeValueChanged changed =>
Clients.Client(connectionId).SendAsync("OnAttributeChanged", changed),
hubClients.Client(connectionId).SendAsync("OnAttributeChanged", changed),
AlarmStateChanged changed =>
Clients.Client(connectionId).SendAsync("OnAlarmChanged", changed),
hubClients.Client(connectionId).SendAsync("OnAlarmChanged", changed),
DebugViewSnapshot snapshot =>
Clients.Client(connectionId).SendAsync("OnSnapshot", snapshot),
hubClients.Client(connectionId).SendAsync("OnSnapshot", snapshot),
_ => Task.CompletedTask
};
},
onTerminated: () =>
{
_ = Clients.Client(connectionId).SendAsync("OnStreamTerminated", "Site disconnected");
_ = hubClients.Client(connectionId).SendAsync("OnStreamTerminated", "Site disconnected");
});
Context.Items[SessionIdKey] = session.SessionId;