using Opc.Ua; namespace ZB.MOM.WW.OtOpcUa.OpcUaServer.IntegrationTests; /// /// 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 , without blocking the reporter or holding the node /// manager's Lock — so the condition's later events are deferred, as they were behind a full SDK channel before /// folder items were delivered per item. /// /// /// The pre-#855 tests produced deferral by holding a folder item's server-side MonitoredItem 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 (). 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. /// internal sealed class NotifierStall : IDisposable { private readonly OtOpcUaNodeManager _nm; private readonly TaskCompletionSource _released = new(TaskCreationOptions.RunContinuationsAsynchronously); private readonly Func _probe; private NotifierStall(OtOpcUaNodeManager nm, string notifierId) { _nm = nm; _probe = node => !_released.Task.IsCompleted && node.NodeId.IdentifierAsString == notifierId ? _released.Task : null; nm.NotifierDeliveryStall = _probe; } /// Starts stalling delivery at the notifier whose NodeId identifier is . public static NotifierStall Hold(OtOpcUaNodeManager nm, string notifierId) => new(nm, notifierId); /// Lets every stalled walk continue (on pool threads, off Lock). public void Release() => _released.TrySetResult(); public void Dispose() { Release(); if (ReferenceEquals(_nm.NotifierDeliveryStall, _probe)) _nm.NotifierDeliveryStall = null; } }