fix(opcua): #855 review 4 — per-item "known" in the folder cut; pin the post-stall liveness check
- RefreshDirectItemsWithCut keys its "decided off Lock" set per (item, condition), so a condition one item's walk saw is still decided for another. - The deferral warning no longer blames slow folder subscribers (no folder of ours defers since #855). - FolderEventFloodWireTests' removal test now asserts the folder item got nothing and the Server object exactly the pre-stall event (it passed with the post-stall liveness re-check deleted). Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
@@ -1806,9 +1806,9 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2, INodeManager3
|
||||
{
|
||||
_lastDeferralWarningUtc = now;
|
||||
_logger.LogWarning(
|
||||
"OtOpcUaNodeManager: condition events are being deferred — an event notifier's delivery channel is "
|
||||
+ "full (first: {NodeId}). Folder event subscribers are not keeping up, or Lock is held across a large "
|
||||
+ "burst; events stay ordered per condition, and past {PerCondition} waiting per condition the oldest "
|
||||
"OtOpcUaNodeManager: condition events are being deferred — a notifier hop the node manager does not "
|
||||
+ "serve itself suspended (first: {NodeId}); since #855 no folder of ours does, so this is unexpected. "
|
||||
+ "Events stay ordered per condition, and past {PerCondition} waiting per condition the oldest "
|
||||
+ "give way to the newest (Gitea #838)",
|
||||
condition.NodeId, MaxDeferredEventsPerCondition);
|
||||
}
|
||||
@@ -6948,7 +6948,8 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2, INodeManager3
|
||||
// Verdicts per (item, condition) before Lock, from a live walk: they depend on the event type, source, notifier
|
||||
// wiring and grants, not on the condition's state.
|
||||
var allowed = new HashSet<(IEventMonitoredItem, NodeState)>();
|
||||
var known = new HashSet<NodeState>(System.Collections.Generic.ReferenceEqualityComparer.Instance);
|
||||
// Per (item, condition): a condition one item's walk saw is still undecided for another item (#855 review 4).
|
||||
var known = new HashSet<(IEventMonitoredItem, NodeState)>();
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (AttachedNotifierOf(item) is not { } notifier) continue;
|
||||
@@ -6957,7 +6958,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2, INodeManager3
|
||||
foreach (var e in events)
|
||||
{
|
||||
if (ConditionOf(e) is not { } node) continue;
|
||||
known.Add(node);
|
||||
known.Add((item, node));
|
||||
if (IsDirectRefreshEventAllowed(item, e, enforced)) allowed.Add((item, node));
|
||||
}
|
||||
}
|
||||
@@ -7015,7 +7016,7 @@ public sealed class OtOpcUaNodeManager : CustomNodeManager2, INodeManager3
|
||||
// A condition materialised since the verdicts were taken is decided here (rare).
|
||||
if (!allowed.Contains((item, instance)))
|
||||
{
|
||||
if (known.Contains(instance)) continue;
|
||||
if (known.Contains((item, instance))) continue;
|
||||
if (!IsDirectRefreshEventAllowed(item, target, enforced)) continue;
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -193,7 +193,10 @@ public sealed class FolderEventFloodWireTests
|
||||
Flooded(onServer).ShouldNotBeEmpty();
|
||||
// None of the deferred tail — which was still waiting when the condition was removed — reached anyone.
|
||||
Flooded(onServer).Max().ShouldBeLessThan(FloodSize - 1, "no ghost reached the Server-object subscriber");
|
||||
Flooded(onA).DefaultIfEmpty(-1).Max().ShouldBeLessThan(FloodSize - 1, "no ghost reached the folder subscriber");
|
||||
// Only the in-flight f0 ever reached the stalled EqA hop, and the condition was gone when the stall released: the
|
||||
// post-stall liveness re-check must have withheld it (#855 review 4 — "< FloodSize - 1" passed without that check).
|
||||
Flooded(onA).ShouldBeEmpty("no ghost reached the folder subscriber");
|
||||
Flooded(onServer).ShouldBe([0], "only the event reported before the stall reached the Server object");
|
||||
}
|
||||
|
||||
private static AlarmConditionSnapshot State(string message) =>
|
||||
|
||||
Reference in New Issue
Block a user