feat(raw): hot-rebind a renamed raw tag's live value without a node recreate #489
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?
Deferred follow-up from v3 Batch 4 (
docs/plans/2026-07-16-v3-batch4-PR.md).Renaming a raw tag correctly rewrites the address space on deploy — the old raw NodeId goes, the new one appears, and the planner re-points the referencing UNS node. This was live-verified during the Batch 4 gate (
HR200→HR200X).But the renamed tag's live value does not rebind until the node is recreated/restarted. Current operational workaround is deploy-THEN-recreate, which is documented but is a sharp edge: an operator who renames a tag and does not restart sees a materialised node that never receives data.
Related sharp edge recorded in the same gate: an absolute RawPath script literal pointing at the old name correctly fails
Bad 0x80000000after the rename — that part is working as intended and is a separate concern from the value rebind.Code-trace only — NOT verified live, and deliberately not "fixed" on that basis. Recording it so the next attempt starts from evidence rather than re-deriving it.
Tracing the rename path on current master (
123ddc3f), the rebind plumbing appears to be complete, which means the symptom recorded at the Batch 4 gate is either already fixed by later work or has a different cause than "the maps are stale":Tag.Nameis part of the RawPath, and the RawPath is inside the driver's merged config —DeploymentArtifactfoldsDriverConfig+ per-deviceDeviceConfig+ the driver'sRawTagsinto one blob viaDriverDeviceConfigMerger.Merge. So a rename changesDriverInstanceSpec.DriverConfig.DriverSpawnPlan.Computecomparessnap.LastConfigJsontospec.DriverConfig(Ordinal) → the driver lands inToApplyDelta.ApplyChildDeltaTellsApplyDelta;DriverInstanceActor.HandleApplyDeltaAsyncis aReceiveAsync, so the child's mailbox is suspended untilReinitializeAsynccompletes. ForModbusDriverthat isShutdown+Initialize, which clears and rebuilds_tagsByRawPathfrom the newRawTags.ReconcileDriversFromBlobruns beforePushDesiredSubscriptionsFromArtifactin the same apply, and both Tell the same child. Mailbox ordering therefore guaranteesSetDesiredSubscriptions(carrying the new RawPaths) is processed after reinit — and itsConnectedhandler self-TellsSubscribe, which drops the prior handle and re-subscribes.PushDesiredSubscriptionsalsoClear()s and rebuilds_nodeIdByDriverRef/_driverRefByNodeIdfrom the new composition, so the value fan-out is re-pointed too.If the value still fails to rebind, the interesting suspects are therefore not the maps but: the re-subscribe racing the driver's poll-engine rebuild, or the OPC UA node being re-materialised while the old subscription's last value is still latched.
Why I stopped rather than shipping a change: the whole chain above says a redeploy already rebinds, so any "fix" written now would be aimed at a mechanism I have not observed failing. Repro first — that was the lesson from #503, where the plausible hypothesis in the issue was wrong and the real cause was somewhere else entirely.
Repro recipe for the next attempt (docker-dev; the rig has
pymodbus/plc/HR200Xon driverMAIN-modbus):read ns=2;s=pymodbus/plc/HR200X-> confirm Good with a live value./rawUI or aTag.NameUPDATE) and deploy — without restarting any node.read ns=2;s=pymodbus/plc/<NewName>. Bug reproduces iff this staysBadWaitingForInitialData/no data while the node keeps running.Blocked here only because Docker Desktop on the dev machine went down mid-session (daemon reports no server version;
open -a Dockerdid not bring it back), so the live step could not run.Worth noting for whoever picks this up: #488's new periodic subscription reconcile does NOT cover this case. It re-subscribes only when the actor holds no handle; after a rename the handle is present (just pointing at the old ref set), which is the "handle present but dead" case explicitly deferred as option B in that issue.
Closed as already fixed — by #516, not by new work here. Re-ran the repro recipe from the 2026-07-26 comment on docker-dev today. It does not reproduce.
Live evidence (docker-dev,
central-1,0 restarts, up since 14:58Z)read ns=2;s=pymodbus/plc/HR200X->3395, Good, live timestamp.UPDATE Tag SET Name='HR200Z'+POST /api/deployments. Sealed in 155 ms, all nodes acked. No node touched.read ns=2;s=pymodbus/plc/HR200Z->3485, Good, fresh source timestamp. Old NodeIdHR200X->0x80340000BadNodeIdUnknown (correct — it is gone). UNS projectionns=3;s=gate481/line1/press1/hr200-> Good.docker inspectconfirms0 restarts, started 14:58:10Z — the rename+deploy at 15:56Z rebound with the node running throughout.The mechanism is not what the code-trace predicted
The 2026-07-26 comment traced this to
ApplyDelta->ReinitializeAsyncrebuilding_tagsByRawPath. That is not what happens. The node log shows a full child respawn:#516 is the fix.
DriverSpawnPlan.Computenow moves a changedDriverConfigout oftoDeltaand intotoStop+toSpawn, so the factory is the single parse authority. A rename changesTag.Name, which is inside the RawPath, whichDriverDeviceConfigMerger.Mergefolds into the mergedDriverConfig— so a rename always trips the respawn branch and the driver is rebuilt against the new RawPaths. ThetoDeltapath the comment traced is now effectively unreachable for a config edit.This is worth recording because it means the fix was a side effect of a change made for an unrelated reason (#516 was about five drivers silently discarding in-place config edits). Nobody would have found it by looking for it.
Also gated the sibling case the issue did not cover
RawPath has more segments than the tag name, so I renamed the device too (
plc->plcz, the middle segment). Identical result:ns=2;s=pymodbus/plcz/HR200Zserved live ~1 Hz data after the deploy, same stop/spawn/subscribe log, no restart. So the whole rename family rebinds, not just the leaf.One flip side, now documented
A rename reconnects the driver — briefly dropping every tag on it, not just the renamed one. That is the cost #516 explicitly accepts ("a correct reconnect beats a silent no-op"); it is not specific to renames, but an operator renaming one tag would not expect it, so it is now called out in
docs/Raw.md.Changes
Docs only, no code —
112f62e5onmaster.docs/Raw.mdgains the operator-facing fact + the reconnect caveat; the Batch-4 PR record's caveat at2026-07-16-v3-batch4-PR.md:82is marked superseded rather than rewritten. Rig restored toHR200X/plc(which was itself a third successful rename-rebind).