Mid-connect ApplyDelta: Calculation and Galaxy ignore the adopted config; no DeltaApplied re-register #664
Reference in New Issue
Block a user
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while fixing #663.
Defect (latent). An
ApplyDeltathat reaches a driver while it is Connecting/Reconnecting goes throughDriverInstanceActor.AdoptConfigDuringInit, which re-runsIDriver.InitializeAsync(newConfigJson)and acks success. Since #516/#630 every driver'sInitializeAsyncre-parses the supplied JSON, except two:CalculationDriver.InitializeAsyncignores the JSON. Calc-tag adds, removes and script edits in the delta are dropped silently, and the deploy still acks success.GalaxyDriver.InitializeAsyncignores the JSON. ItsReinitializeAsyncdeliberately refuses a non-equivalent config (NotSupportedException, "restart the DriverInstance"), but the mid-connect path never reaches that refusal. The driver silently connects on its old gateway endpoint, client name and reconnect policy.Separately, a mid-connect adoption never sends
DeltaAppliedto the host, so a dependency consumer such as Calculation keeps its OLD mux interest even once the driver adopts the new tag set.Reachability. Not reachable from a deploy today: since #516 a DriverConfig change is stop + respawn, so
ApplyChildDeltais never called.Proposed fix.
InitializeAsyncadopts a config with a real body (HasConfigBody, the same convention as the other drivers): rebuild the tag table and drop compiled scripts before arming timers.InitializeAsyncruns the same equivalence checkReinitializeAsyncuses, so a non-equivalent config is REFUSED loudly instead of ignored. Hot-swapping Galaxy options is out of scope by design.DeltaAppliedso the host re-registers the dependency-consumer adapter.Fixed on master in
f622d0be(merging97fcf624).Calculation.
InitializeAsyncnow adopts a config that has a real body. It rebuilds the tag table through the sameAdoptConfigroutineReinitializeAsyncuses, so the two paths cannot drift, and it disposes any running timer scheduler before arming a new one (#630 hygiene: re-initialising without a shutdown used to start a second scheduler). A"{}"or empty document keeps the constructor tags.Galaxy. It cannot hot-swap its options, so
InitializeAsyncnow runs the same equivalence check asReinitializeAsync(extracted toRefuseNonEquivalentConfig). A non-equivalent config is refused withNotSupportedExceptioninstead of being ignored, so the driver stays Reconnecting and the failure is logged instead of connecting on the old endpoint. RawTags remain excluded from the equivalence check, as they already were for reinit. A normal spawn passes the factory's own JSON, which is always equivalent.Actor. A mid-connect adoption now sends
DeltaAppliedonce, after the init on the adopted config succeeds, so the host re-registers the dependency-consumer adapter.Tests. Each new test fails when its production change is reverted:
InitializeAsync_on_a_live_driver_adopts_a_config_with_a_body(Calculation)InitializeAsync_refuses_a_non_equivalent_config_and_accepts_an_equivalent_one(Galaxy)A_mid_connect_adoption_notifies_DeltaApplied_once_after_the_adopted_init_succeeds(actor; the "once" half has a reconnect positive control)Two existing Galaxy tests initialised a driver with a config that was not equivalent to its constructor options; they now use the
"{}"lifecycle shape. I dropped a timer-leak test because it was vacuous: the timer callback is gated on_running, so a leaked scheduler is invisible after shutdown. That hygiene fix is untested.Full suite on the merged master: 13219 passed, 0 failed.
The ack is still sent on adoption, so a refused Galaxy change acks success and then fails its connects. This is the existing
AdoptConfigDuringInitcontract, and none of this is reachable from a deploy since #516. Docs:docs/DriverLifecycle.md,CLAUDE.md. The scadaproj history entry is0b056c6; ScadaBridge impact is none.