Fix second-pass review findings: subscription leak on rebuild, metrics accuracy, and MxAccess startup recovery

- Preserve and replay subscription ref counts across address space rebuilds to prevent MXAccess subscription leaks
- Mark read timeouts and write failures as unsuccessful in PerformanceMetrics for accurate health reporting
- Add deferred MxAccess reconnect path when initial connection fails at startup
- Update code review document with verified completions and new findings
- Add covering tests for all fixes

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Joseph Doherty
2026-03-25 09:41:12 -04:00
parent 71254e005e
commit 09ed15bdda
12 changed files with 307 additions and 51 deletions

View File

@@ -102,6 +102,42 @@ namespace ZB.MOM.WW.LmxOpcUa.Tests.MxAccess
callbackInvoked.ShouldBe(true);
}
[Fact]
public async Task OneShotRead_DoesNotRemovePersistentSubscription_OnReconnect()
{
await _client.ConnectAsync();
var callbackInvoked = false;
await _client.SubscribeAsync("TestTag.Attr", (_, _) => callbackInvoked = true);
var readTask = _client.ReadAsync("TestTag.Attr");
await Task.Delay(50);
_proxy.SimulateDataChangeByAddress("TestTag.Attr", 42, 192);
(await readTask).Value.ShouldBe(42);
callbackInvoked = false;
await _client.ReconnectAsync();
_proxy.SimulateDataChangeByAddress("TestTag.Attr", "after_reconnect", 192);
callbackInvoked.ShouldBe(true);
_client.ActiveSubscriptionCount.ShouldBe(1);
}
[Fact]
public async Task OneShotWrite_DoesNotBreakPersistentUnsubscribe()
{
await _client.ConnectAsync();
await _client.SubscribeAsync("TestTag.Attr", (_, _) => { });
_proxy.Items.Values.ShouldContain("TestTag.Attr");
var writeResult = await _client.WriteAsync("TestTag.Attr", 7);
writeResult.ShouldBe(true);
await _client.UnsubscribeAsync("TestTag.Attr");
_client.ActiveSubscriptionCount.ShouldBe(0);
_proxy.Items.Values.ShouldNotContain("TestTag.Attr");
}
[Fact]
public async Task ProbeTag_SubscribedOnConnect()
{