fix(runtime): in-place ApplyDelta releases and re-subscribes data + alarms (#663)

A Connected ApplyDelta called ReinitializeAsync and kept the actor's data and
native-alarm handles, though every driver's reinit drops the driver-side
subscriptions behind them (#630). The reconcile only re-subscribes a missing
handle, so the driver went silent until a reconnect. Release both at the
driver before the reinit and re-subscribe both in a finally. Unreachable from
deploys since #516; pinned by a two-case test, each case failing against a
different wrong fix.

Claude-Session: https://claude.ai/code/session_01Ef2ozxS7QPanGT8uoATbTe
This commit is contained in:
Joseph Doherty
2026-10-09 07:49:56 -04:00
parent 83d129d1bd
commit 39c5af3d7f
4 changed files with 158 additions and 8 deletions
+9
View File
@@ -255,6 +255,15 @@ Things to know before touching any of this:
instead of gating the feed for the life of the connection. OpcUaClient removes a half-built alarm
subscription from its session when the subscribe throws, so those retries do not accumulate.
End-to-end pin: `NativeAlarmFeedSurvivesDeployTests`.
- **An in-place `ApplyDelta` on a Connected driver releases and re-establishes BOTH subscriptions (#663).**
`ReinitializeAsync` drops every driver-side subscription (#630), but the handler used to keep the actor's
handles, and the reconcile only re-subscribes a *missing* handle — so the driver went silent until a
reconnect. It now releases data and alarms at the driver first (explicitly, so a driver whose reinit keeps
its subscriptions is not left holding two), reinitialises, and re-subscribes both in a `finally` (a failed
reinit has lost them too). It stays on `ReinitializeAsync` rather than shutdown + initialise because
Calculation and Galaxy take their config at construction and ignore the JSON passed to `InitializeAsync`.
Not reachable from a deploy since #516 (a DriverConfig change respawns). Pin:
`An_in_place_ApplyDelta_leaves_one_live_data_and_one_live_alarm_subscription` (one case per wrong fix).
- **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