worker(alarms): UnAdvise only advised handles in LmxSubtagAlarmSource teardown

B3: track advised handles separately from added handles so Dispose only UnAdvises
items that were actually advised — a write-only subtag (e.g. ack-comment added by
Write, never advised) is removed but not unadvised. Add Dispose tests covering the
advised/write-only split, full removal, single Unregister, and double-dispose
idempotency.
This commit is contained in:
Joseph Doherty
2026-06-14 02:35:59 -04:00
parent a3752799de
commit 986dcee14a
2 changed files with 79 additions and 10 deletions
@@ -46,6 +46,12 @@ public sealed class LmxSubtagAlarmSource : ISubtagAlarmSource
private readonly Dictionary<int, string> addressesByItemHandle =
new Dictionary<int, string>();
// Handles that were actually Advise()d, tracked separately from the added
// set so Dispose only UnAdvises advised items. Write() can AddItem a
// write-only subtag (e.g. an ack-comment that was never advised); calling
// UnAdvise on such a handle would be an unbalanced teardown.
private readonly HashSet<int> advisedItemHandles = new HashSet<int>();
private object? mxAccessComObject;
private IMxAccessServer? server;
private LMXProxyServerClass? comEventSource;
@@ -134,6 +140,7 @@ public sealed class LmxSubtagAlarmSource : ISubtagAlarmSource
mxServer.Advise(serverHandle, itemHandle);
itemHandlesByAddress[itemAddress!] = itemHandle;
addressesByItemHandle[itemHandle] = itemAddress!;
advisedItemHandles.Add(itemHandle);
}
}
@@ -225,7 +232,13 @@ public sealed class LmxSubtagAlarmSource : ISubtagAlarmSource
{
foreach (KeyValuePair<int, string> entry in addressesByItemHandle)
{
try { mxServer.UnAdvise(serverHandle, entry.Key); } catch { /* swallow — best effort */ }
// Only UnAdvise handles that were actually advised; a write-only
// item (added by Write but never Advise'd) was never advised.
if (advisedItemHandles.Contains(entry.Key))
{
try { mxServer.UnAdvise(serverHandle, entry.Key); } catch { /* swallow — best effort */ }
}
try { mxServer.RemoveItem(serverHandle, entry.Key); } catch { /* swallow — best effort */ }
}
@@ -234,6 +247,7 @@ public sealed class LmxSubtagAlarmSource : ISubtagAlarmSource
itemHandlesByAddress.Clear();
addressesByItemHandle.Clear();
advisedItemHandles.Clear();
object? comToRelease = mxAccessComObject;
mxAccessComObject = null;