fix(galaxy): authoritative handle resolution + review cleanups
Make SubscriptionRegistry.TryResolveItemHandle confirm a live subscription genuinely binds fullRef->handle (via the reverse index) rather than trusting the forward-map hint + a bare liveness check. Fixes the cross-ref-same-handle hazard (wrong-tag borrow) while preserving the legitimate multiple-subscriptions-per-tag borrow. Adds cross-ref + same-ref-multi-sub tests; drops a duplicate SubscriptionEntry <summary>; documents the writer's supervisory-advise reconnect lifecycle.
This commit is contained in:
+39
@@ -114,4 +114,43 @@ public sealed class SubscriptionRegistryHandleResolveTests
|
||||
// must return null regardless of whether the forward entry was cleaned up.
|
||||
registry.TryResolveItemHandle("Tag.A").ShouldBeNull();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A tag may legitimately appear in multiple driver subscriptions (separate OPC UA monitored
|
||||
/// items on the same Galaxy attribute) — they share one gw item handle. Removing ONE of them
|
||||
/// must keep the tag resolvable while another subscription still binds it, so the writer keeps
|
||||
/// borrowing instead of falling back to AddItem.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Remove_OneOfTwoSubscribersForSameRef_StillResolves()
|
||||
{
|
||||
var registry = new SubscriptionRegistry();
|
||||
registry.Register(1, [new TagBinding("Tag.A", 5)]);
|
||||
registry.Register(2, [new TagBinding("Tag.A", 5)]);
|
||||
|
||||
registry.Remove(1);
|
||||
|
||||
// sub2 still binds Tag.A -> 5, so the borrow must still be offered.
|
||||
registry.TryResolveItemHandle("Tag.A").ShouldBe(5);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Authoritative resolution: if two DIFFERENT refs ever map to the same numeric handle and the
|
||||
/// subscription for one is removed, that ref must NOT resolve to the handle that now belongs to
|
||||
/// the other ref — a wrong-tag write would be the worst outcome. Resolution confirms a live
|
||||
/// subscription genuinely binds fullRef -> handle, not just that the handle is alive.
|
||||
/// </summary>
|
||||
[Fact]
|
||||
public void Remove_CrossRefSameHandle_DoesNotResolveToTheOtherRefsHandle()
|
||||
{
|
||||
var registry = new SubscriptionRegistry();
|
||||
registry.Register(1, [new TagBinding("Tag.A", 5)]);
|
||||
registry.Register(2, [new TagBinding("Tag.B", 5)]);
|
||||
|
||||
registry.Remove(1);
|
||||
|
||||
// Tag.A's subscription is gone; handle 5 is still alive but now only Tag.B binds it.
|
||||
registry.TryResolveItemHandle("Tag.A").ShouldBeNull();
|
||||
registry.TryResolveItemHandle("Tag.B").ShouldBe(5);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user