feat(otopcua): re-inject discovered nodes after address-space rebuild

This commit is contained in:
Joseph Doherty
2026-06-26 08:36:52 -04:00
parent b1e4fba792
commit 0788bad145
2 changed files with 148 additions and 5 deletions
@@ -1243,6 +1243,40 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
// reports its FixedTree. Set here (not in ApplyAndAck) so both the fresh-apply and bootstrap-restore
// paths — which both route through this method — leave a current composition.
_lastComposition = composition;
// Re-inject discovered (FixedTree) nodes after the authored rebuild. PushDesiredSubscriptions cleared
// _nodeIdByDriverRef and re-pushed authored-only subscriptions above; without this, every redeploy /
// bootstrap-restore would drop the injected FixedTree routes + materialised nodes until the driver
// happens to reconnect and re-discover. Re-resolve each cached driver's equipment from the CURRENT
// composition; drop the cache entry if the driver/equipment no longer resolves to exactly one (a rebind
// or removal — the driver's next reconnect re-discovery will rebuild it cleanly).
foreach (var driverId in _discoveredByDriver.Keys.ToList()) // snapshot — we mutate the dict below
{
var plan = _discoveredByDriver[driverId];
var equipmentIds = composition.EquipmentTags
.Where(t => string.Equals(t.DriverInstanceId, driverId, StringComparison.Ordinal))
.Select(t => t.EquipmentId)
.Distinct(StringComparer.Ordinal)
.ToList();
if (equipmentIds.Count != 1)
{
_discoveredByDriver.Remove(driverId);
_log.Debug("DriverHost {Node}: dropped cached discovered nodes for {Driver} — equipment no longer resolves uniquely", _localNode, driverId);
continue;
}
var equipmentId = equipmentIds[0];
// If the equipment was rebound (the cached plan's NodeIds are scoped to the OLD equipment), drop +
// let re-discovery rebuild against the new equipment. The plan's NodeIds are "{equipmentId}/...".
var planEquipmentConsistent = plan.Variables.Count > 0
&& plan.Variables[0].NodeId.StartsWith(equipmentId + "/", StringComparison.Ordinal);
if (!planEquipmentConsistent)
{
_discoveredByDriver.Remove(driverId);
_log.Debug("DriverHost {Node}: dropped cached discovered nodes for {Driver} — equipment rebound", _localNode, driverId);
continue;
}
ApplyDiscoveredPlan(driverId, equipmentId, plan);
}
}
private void SpawnChild(DriverInstanceSpec spec)