diff --git a/src/Server/ZB.MOM.WW.OtOpcUa.ControlPlane/Communication/CentralCommunicationActor.cs b/src/Server/ZB.MOM.WW.OtOpcUa.ControlPlane/Communication/CentralCommunicationActor.cs index f400ea17..0c470ede 100644 --- a/src/Server/ZB.MOM.WW.OtOpcUa.ControlPlane/Communication/CentralCommunicationActor.cs +++ b/src/Server/ZB.MOM.WW.OtOpcUa.ControlPlane/Communication/CentralCommunicationActor.cs @@ -258,8 +258,9 @@ public sealed class CentralCommunicationActor : ReceiveActor, IWithTimers kv => kv.Key, kv => kv.Value.ToImmutableHashSet()); // Clusters that dropped out of the DB entirely, or whose only rows were malformed this refresh: - // stop and forget their clients so a vanished cluster stops receiving. A cluster whose rows all - // went malformed lands here via the empty-set branch. + // stop and forget their clients so a vanished cluster stops receiving. LoadContactsFromDb only + // creates a ByCluster key when at least one of its rows parsed, so an all-malformed cluster has + // NO key at all and is caught here by the absent-key (!TryGetValue) arm — not by an empty value. var gone = _clients.Keys .Where(id => !byCluster.TryGetValue(id, out var c) || c.IsEmpty) .ToList(); @@ -277,8 +278,10 @@ public sealed class CentralCommunicationActor : ReceiveActor, IWithTimers { if (contacts.IsEmpty) { - // Only-malformed rows for this cluster this refresh. Warn per cluster; its client (if - // any) was already stopped above. + // Defensive: LoadContactsFromDb never emits an empty-but-present cluster (a key is + // created only on a first successfully-parsed row), so this arm is unreachable for the + // DB producer — an all-malformed cluster is handled by the absent-key branch above. It + // guards a ContactsLoaded (a public record) constructed elsewhere with an empty list. _log.Warning( "Cluster {ClusterId} has no usable ClusterNode contact points; no ClusterClient " + "was created for it and its nodes' commands will be dropped until a refresh finds "