fix(opcuaclient): #676 re-review — trailing read skips refs delivered to while in flight
1. A per-ref delivery counter (bumped under the delivery lock by every publish path) is captured before the trailing read is issued; a ref delivered to since is skipped. Without source timestamps the read's sample could not be ordered against a notification that landed mid-read, and publishing it regressed the node. Test injects the notification inside the mocked read, with and without a read source time (red before). 2. Delivery-lock comment corrected (it serialises across subscriptions, which the SDK dispatches on separate workers); PublishCommsLoss now publishes under the lock too. 3. CancelSessionWork cancels without disposing, so a keep-alive thread reading the old token cannot hit ObjectDisposedException.
This commit is contained in:
@@ -113,13 +113,15 @@ The session keep-alive (`KeepAliveInterval`, default 5 s) is the liveness signal
|
||||
source timestamp (a server that does not send them) is the server's current state and counts as newer. An **equal**
|
||||
source timestamp with different content IS published — it is the server's current state, not a replay, and it repairs
|
||||
the equal-timestamp status-only change the replay guard drops by design. An identical (value, status) is never
|
||||
republished. An Information line names how many refs it had to repair. The trailing read and the replay-guard disarm
|
||||
republished. A ref that received ANY delivery while the read was in flight is skipped (a per-ref delivery counter is
|
||||
captured before the read is issued): without source timestamps the read's sample cannot be ordered against that
|
||||
notification, and publishing it would regress the node. An Information line names how many refs it had to repair. The trailing read and the replay-guard disarm
|
||||
timer run on the driver clock and are cancelled when the session is released (re-initialise / shutdown / dispose).
|
||||
- **No older value is ever delivered after a newer one.** The three paths that publish upstream data — live
|
||||
notifications, the recovery read and the trailing read — make their "may I publish this?" decision and the publish
|
||||
inside one driver-wide critical section. A single lock rather than one per ref: a session's notifications arrive on
|
||||
its publish thread one message at a time, so the only contention is a recovery/trailing read overlapping a
|
||||
notification, and an uncontended lock costs tens of nanoseconds per notification. Subscribers run inside it — the
|
||||
- **No older value is ever delivered after a newer one.** The paths that publish upstream data — live notifications,
|
||||
the recovery read, the trailing read and the comms-loss Bad fan — make their "may I publish this?" decision and the
|
||||
publish inside one driver-wide critical section. A single lock rather than one per ref: it also serialises delivery
|
||||
across subscriptions (the SDK dispatches each subscription's messages on its own worker), which is cheap because each
|
||||
section is a dictionary update plus a subscriber `Tell` and nothing awaits under it. Subscribers run inside it — the
|
||||
host's handler only `Tell`s an actor — so a subscriber must never block on, or call back into, the driver.
|
||||
- **The session does not come back** (an upstream restart: the server forgot the session) ⇒ the driver stays
|
||||
`Degraded` with its read stamp frozen, which is the #527 comms-loss watchdog's trip condition; `DriverInstanceActor`
|
||||
|
||||
Reference in New Issue
Block a user