fix(lmxproxy): fix orphaned tag subscriptions when client subscribes per-tag

When a client calls Subscribe multiple times with the same session ID
(one tag per RPC), each call overwrites the ClientSubscription entry.
UnsubscribeClient only cleaned up tags from the last entry, leaving
earlier tags orphaned in _tagSubscriptions. Now scans all tag
subscriptions for client references during cleanup.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-03-24 15:43:29 -04:00
parent eecd82b787
commit 4db93cae2b

View File

@@ -57,8 +57,8 @@ namespace ZB.MOM.WW.LmxProxy.Host.Subscriptions
}); });
var addressSet = new HashSet<string>(addresses, StringComparer.OrdinalIgnoreCase); var addressSet = new HashSet<string>(addresses, StringComparer.OrdinalIgnoreCase);
var clientSub = new ClientSubscription(clientId, channel, addressSet);
var clientSub = new ClientSubscription(clientId, channel, addressSet);
_clientSubscriptions[clientId] = clientSub; _clientSubscriptions[clientId] = clientSub;
var newTags = new List<string>(); var newTags = new List<string>();
@@ -170,17 +170,18 @@ namespace ZB.MOM.WW.LmxProxy.Host.Subscriptions
_rwLock.EnterWriteLock(); _rwLock.EnterWriteLock();
try try
{ {
foreach (var address in clientSub.Addresses) // Scan all tag subscriptions — not just clientSub.Addresses — because
// a client may have called Subscribe multiple times (one tag per RPC),
// each overwriting the ClientSubscription. The last one's Addresses
// only has the final batch, but earlier tags still reference this client.
foreach (var kvp in _tagSubscriptions)
{ {
if (_tagSubscriptions.TryGetValue(address, out var tagSub)) if (kvp.Value.ClientIds.Remove(clientId))
{ {
tagSub.ClientIds.Remove(clientId); if (kvp.Value.ClientIds.Count == 0)
// Last client unsubscribed — remove the tag subscription
if (tagSub.ClientIds.Count == 0)
{ {
_tagSubscriptions.TryRemove(address, out _); _tagSubscriptions.TryRemove(kvp.Key, out _);
tagsToDispose.Add(address); tagsToDispose.Add(kvp.Key);
} }
} }
} }