perf(opcua): #855 — shared per-report projection for condition events under Lock
Condition events delivered to many event items (folder and Server-object) no longer pay the SDK's per-item QueueEvent(IFilterTarget) work under the node-manager Lock: the role-permission verdict is shared per (session, identity), the #826 grant check per identity per hop, and the where clause + select-clause projection per (value-equal EventFilter, locales); each item gets its own EventFieldList over the shared read-only fields. The SDK's QueueEvent body is reproduced under the item's own lock (three private SDK members, guard-tested); anything outside that shape falls back to the SDK. The per-report duplicate check replaces the SDK's O(queue) scan. ProjectedEventDeliveryWireTests compares both paths over the wire. Claude-Session: https://claude.ai/code/session_01GT67AD99zQQqpLCVm3p7xk
This commit is contained in:
@@ -100,3 +100,29 @@ The per-event verdict for folder items moves from the SDK's consumer task (cache
|
||||
ConditionRefresh2, resubscribe), the gate race, newer-than-cut replay, ACL omission not withheld, ACL revocation during
|
||||
deferral, the in-flight suspended event, and two stress tests (production overload with no stall; stall + refresh +
|
||||
reports concurrently).
|
||||
|
||||
## Follow-up: cost under `Lock` (projected delivery)
|
||||
|
||||
Measured with `FolderEventDeliveryPerfTests` (opt-in, `OTOPCUA_EVENT_PERF=1`): the per-item work moved under `Lock` by
|
||||
this change cost ~5–6 µs/item publishing and ~10–30 µs/item at a full queue (N = 50 stalled items: p50 0.4–1.6 ms).
|
||||
Profiling split it into the SDK's role-permission check (~3–9 µs/item) and `MonitoredItem.QueueEvent(IFilterTarget)`,
|
||||
whose duplicate scan over the last ≤ 1000 queued events grows with the queue (~3 µs at QueueSize 10, ~10–16 at 1000,
|
||||
~16–31 at 10 000) on top of the per-item where-clause + select-clause projection.
|
||||
|
||||
`ProjectedEventDelivery` / `EventDeliveryCache` (per report, hung on the condition snapshot, dropped when the walk ends):
|
||||
|
||||
- the SDK role verdict once per (session, effective identity); the #826 grant check once per identity per hop;
|
||||
- the where clause and the select-clause projection once per (value-equal `EventFilter`, preferred locales) — filters
|
||||
are grouped once per filter object (`Utils.IsEqual`, ≤ 4096 shared groups) — and the field values shared read-only by
|
||||
every item queued from it (each item still gets its own `EventFieldList`: the SDK rewrites a queued list's
|
||||
`ClientHandle` on modify);
|
||||
- the SDK's `QueueEvent(instance, false)` body reproduced under the item's own lock (event item; Disabled ⇒ nothing;
|
||||
duplicate ⇒ nothing — asked of the report's "already queued" set instead of the queue; the filter in use;
|
||||
`SetQueueOverflowIfFull`; then the public `QueueEvent(EventFieldList)`), reading three private members (`m_lock`,
|
||||
`m_eventQueueHandler`, `FilterToUse`) — pinned by a guard test; anything else (members missing, a subclassed item, a
|
||||
non-`EventFilter` filter, a `SupportsFilteredRetain` condition, a non-snapshot event, refresh replays) uses the SDK's
|
||||
own `QueueEvent(IFilterTarget)`.
|
||||
|
||||
`ProjectedEventDeliveryWireTests` runs one scenario through each path (`UseProjectedEventDelivery`) and requires
|
||||
identical streams on folder and Server-object items (value-equal filters, a severity where clause, an OfType where
|
||||
clause with an absent field, a Disabled item); it fails if the where clause or a field is projected differently.
|
||||
|
||||
Reference in New Issue
Block a user