feat(otopcua): re-trigger discovery on config-unchanged rebind (follow-up C)
This commit is contained in:
@@ -1313,34 +1313,43 @@ public sealed class DriverHostActor : ReceiveActor, IWithTimers
|
||||
var candidates = fromNodes.Concat(fromTags).ToHashSet(StringComparer.Ordinal);
|
||||
|
||||
var plansByEquipment = _discoveredByDriver[driverId];
|
||||
// Track whether ANY entry was dropped (no-longer-candidate or rebind) so we can re-trigger this
|
||||
// driver's discovery exactly ONCE after the inner map is processed (see the post-loop block).
|
||||
var droppedAny = false;
|
||||
foreach (var equipmentId in plansByEquipment.Keys.ToList()) // snapshot — we mutate the inner dict
|
||||
{
|
||||
var plan = plansByEquipment[equipmentId];
|
||||
if (!candidates.Contains(equipmentId))
|
||||
{
|
||||
plansByEquipment.Remove(equipmentId);
|
||||
droppedAny = true;
|
||||
_log.Debug("DriverHost {Node}: dropped cached discovered nodes for {Driver}/{Equipment} — equipment no longer resolves", _localNode, driverId, equipmentId);
|
||||
continue;
|
||||
}
|
||||
// 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}/...".
|
||||
// KNOWN LIMITATION (follow-up, alongside the multi-device-per-driver limitation): a
|
||||
// CONFIG-UNCHANGED rebind (the driver's DriverConfig is identical, only its authored tag's
|
||||
// EquipmentId moved) drops the cached plan here but does NOT itself re-trigger discovery —
|
||||
// ReconcileDrivers only restarts a child on a DriverConfig change, so a config-unchanged child is
|
||||
// never stopped/reconnected. The FixedTree subtree therefore stays ABSENT under the new equipment
|
||||
// until the driver's next reconnect/restart re-discovers it. We deliberately do NOT add re-trigger
|
||||
// logic here (it would couple the subscription pass to driver-lifecycle control); the drop is the
|
||||
// safe, correct fail-state (a stale EQ-1-scoped graft under EQ-2 would be worse).
|
||||
var planEquipmentConsistent = plan.Variables.Count > 0
|
||||
&& plan.Variables[0].NodeId.StartsWith(equipmentId + "/", StringComparison.Ordinal);
|
||||
if (!planEquipmentConsistent)
|
||||
{
|
||||
plansByEquipment.Remove(equipmentId);
|
||||
droppedAny = true;
|
||||
_log.Debug("DriverHost {Node}: dropped cached discovered nodes for {Driver}/{Equipment} — equipment rebound", _localNode, driverId, equipmentId);
|
||||
}
|
||||
}
|
||||
|
||||
// Re-trigger discovery when ANY entry was dropped (no-longer-candidate or rebind). A CONFIG-UNCHANGED
|
||||
// rebind (the driver's DriverConfig is identical, only its authored tag's EquipmentId moved) is NOT
|
||||
// restarted by ReconcileDrivers — the child stays Connected — so without this nudge the FixedTree
|
||||
// subtree would stay ABSENT under the new equipment until the driver's next natural reconnect. We now
|
||||
// ask the child to re-run discovery so it re-grafts promptly: the next pass resolves against the new
|
||||
// _lastComposition (the now-bound equipment). This is a DISCOVERY action, not lifecycle control — no
|
||||
// stop/restart; it is idempotent, and the child no-ops it if not Connected (handled in
|
||||
// DriverInstanceActor). Sent at most ONCE per driver per re-inject pass (here, after the inner map is
|
||||
// processed — so even when the inner map empties below), guarded on the child still existing.
|
||||
if (droppedAny && _children.TryGetValue(driverId, out var rediscoverEntry))
|
||||
rediscoverEntry.Actor.Tell(new DriverInstanceActor.TriggerRediscovery());
|
||||
|
||||
if (plansByEquipment.Count == 0)
|
||||
{
|
||||
_discoveredByDriver.Remove(driverId);
|
||||
|
||||
Reference in New Issue
Block a user