d9b23e0b95
Condition events reaching one of our notifiers no longer go through the SDK MonitoredNode2 channel: the walk serves the attached items itself, per item and per event (audit guard, uncached INodeManager3 verdict, item gate/cut, QueueEvent), as the Server-object path does. No notifier of ours defers any more; the #838 deferral queue and the #854 cut stay as the safety net and folder items share the cut (RefreshDirectItems, flag on and off). An event whose walk suspends is marked deferred before it suspends. Tests produce deferral with a notifier-stall probe instead of holding a folder item's SDK lock (which would now block the reporter). New FolderConditionRefreshWireTests; design note in docs/plans. Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
43 lines
2.2 KiB
C#
43 lines
2.2 KiB
C#
using Opc.Ua;
|
|
|
|
namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests;
|
|
|
|
/// <summary>
|
|
/// Stalls condition-event delivery at one of the node manager's notifiers (Gitea #855): every condition event whose walk
|
|
/// reaches that notifier suspends there until <see cref="Release"/>, without blocking the reporter or holding the node
|
|
/// manager's <c>Lock</c> — so the condition's later events are deferred, as they were behind a full SDK channel before
|
|
/// folder items were delivered per item.
|
|
/// </summary>
|
|
/// <remarks>
|
|
/// The pre-#855 tests produced deferral by holding a folder item's server-side <c>MonitoredItem</c> lock, which parked
|
|
/// the SDK's per-node delivery task. Folder items are now queued synchronously by the reporter (as Server-object items
|
|
/// always were), so holding an item lock would block the reporter instead; this goes through the node manager's test
|
|
/// probe (<see cref="OtOpcUaNodeManager.NotifierDeliveryStall"/>). The probe is consulted only at a notifier that has
|
|
/// event items (the hop the per-item delivery serves), so the stalled notifier must carry at least one.
|
|
/// </remarks>
|
|
internal sealed class NotifierStall : IDisposable
|
|
{
|
|
private readonly OtOpcUaNodeManager _nm;
|
|
private readonly TaskCompletionSource _released = new(TaskCreationOptions.RunContinuationsAsynchronously);
|
|
private readonly Func<NodeState, Task?> _probe;
|
|
|
|
private NotifierStall(OtOpcUaNodeManager nm, string notifierId)
|
|
{
|
|
_nm = nm;
|
|
_probe = node => !_released.Task.IsCompleted && node.NodeId.IdentifierAsString == notifierId ? _released.Task : null;
|
|
nm.NotifierDeliveryStall = _probe;
|
|
}
|
|
|
|
/// <summary>Starts stalling delivery at the notifier whose NodeId identifier is <paramref name="notifierId"/>.</summary>
|
|
public static NotifierStall Hold(OtOpcUaNodeManager nm, string notifierId) => new(nm, notifierId);
|
|
|
|
/// <summary>Lets every stalled walk continue (on pool threads, off <c>Lock</c>).</summary>
|
|
public void Release() => _released.TrySetResult();
|
|
|
|
public void Dispose()
|
|
{
|
|
Release();
|
|
if (ReferenceEquals(_nm.NotifierDeliveryStall, _probe)) _nm.NotifierDeliveryStall = null;
|
|
}
|
|
}
|