fix(runtime): a changed alarm set re-subscribes instead of latching (review F1)
The alarm subscribe gated on 'a handle exists', which is correct for an idempotent re-push and wrong for a deploy that widens/narrows the set. Combined with the spawn-time desired set, a respawning deploy could latch the PREVIOUS revision's alarm refs for the life of the connection; sourceNodeIds-filtering drivers (OpcUaClient/AbCip/Focas) would never deliver the new alarm. Seen RED: LastSubscribedAlarmRefs stayed [alarmX] after a deploy widening to [alarmX, alarmY].
This commit is contained in:
@@ -211,6 +211,19 @@ Things to know before touching any of this:
|
||||
(`HandleSubscribeAsync` drops the prior subscription before establishing the new one); a real device
|
||||
driver, whose initialise opens a socket, rarely wins that race. Withholding the spawn-time send to avoid
|
||||
it would simply restore 03/S16, since the Restart and supervision routes have no bulk push coming.
|
||||
- ⚠️ **Convergence is not free on the ALARM side, and "a subscription already exists" is the wrong guard
|
||||
there.** The value path converges by construction — `StoreDesiredSubscriptions` is a full replace and
|
||||
`HandleSubscribeAsync` drops the prior subscription before establishing the new one — but
|
||||
`SubscribeDesiredAlarms` / `HandleSubscribeAlarmsAsync` used to gate purely on
|
||||
`_alarmSubscriptionHandle is not null`, so once a handle existed the alarm set **latched for the life of
|
||||
the connection**. Combined with the transient above, a deploy widening a driver's alarm refs `{X}` →
|
||||
`{X, Y}` could leave a fast-initialising driver subscribed to `{X}` forever, and the drivers that filter
|
||||
their feed on the subscribed `sourceNodeIds` (OPC UA Client, AB CIP, FOCAS) would simply never deliver
|
||||
the new alarm — silently. The guard is now "the desired refs differ from the ones the live handle was
|
||||
established with" (`_subscribedAlarmRefs`), and a changed set **releases the old subscription before
|
||||
re-subscribing** so a filtering driver does not accumulate two overlapping ones. There is still
|
||||
deliberately no unsubscribe-on-**empty**: a removed alarm tag's condition node is torn down on the
|
||||
address-space rebuild and `ForwardNativeAlarm` drops any transition whose `ConditionId` no longer maps.
|
||||
- **Restart tests must assert a FUNCTION, never existence.** `RestartDriver_respawns_the_child_without_an_actor_name_collision`
|
||||
asserts only that a child exists after a restart, and it passed green against the dead driver 03/S16
|
||||
describes. It is kept — its own subject, the `InvalidActorNameException`, is real — but the load-bearing
|
||||
|
||||
Reference in New Issue
Block a user