feat(drivers): periodic reconcile of desired vs actual driver subscriptions #488
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?
Raised while reviewing the #485 subscription guard: is there a way to periodically check for actual subscriptions, to defend against races or a subscription dying silently?
What exists today
DriverInstanceActorholds_desiredRefsand re-asserts them on every Connected entry (ResubscribeDesired), so a reconnect self-heals.health-polltimer already exists — but it only callsIDriver.GetHealth()and publishes to the AdminUI. It never touches subscriptions.ISubscriptionHandleis opaque (just aDiagnosticIdstring), so the Core currently cannot ask a driver what it is actually subscribed to.The gap
Nothing detects a subscription that dies while the driver remains Connected — a server-side subscription drop, a session reset the driver does not surface as a disconnect, or a handle lost to an error path. The desired set is only re-applied on a connect transition or a deploy.
Two options
A — cheap, no interface change. A periodic invariant check in
DriverInstanceActor: while Connected, if_desiredRefs.Count > 0but_subscriptionHandle is null, self-tellSubscribe. This is a state-machine consistency check, not a true probe, but it needs no driver support and could ride the existinghealth-polltick.B — real reconcile, needs an interface change. Add a member to
ISubscribable(e.g.Task<IReadOnlyList<string>> GetSubscribedRefsAsync()), or liveness onISubscriptionHandle, then periodically diff desired vs actual and re-subscribe the difference. Requires implementing it across all 7 drivers (Modbus, S7, AB CIP/Legacy, TwinCAT, FOCAS, OpcUaClient) plus Galaxy, each with different subscription mechanics (polled vs native).Suggest A first — it is small and strictly additive — and only take B if a real occurrence shows the handle is present but dead.
Explicitly NOT this issue
This does not address deploy-time drift, which was #486 (a node claiming a revision it never applied). Re-asserting a desired set that is already correct would not have helped there. Keep the two separate.
Implemented on
fix/issues-503-501-493-499-488, commit4b9bcddb— option A, as suggested.ReconcileSubscriptionrides the existing 30 shealth-polltick: while Connected, a driver holding a desired ref set but no live handle is re-subscribed.One addition beyond the sketch: gated on
ISubscribable. A driver that cannot subscribe at all can still be handed a desired set by a deploy, and without the guard it would be toldSubscribeevery tick and fail every tick, forever — turning a safety net into a log-spam generator.Staying honest about what it is: a state-machine consistency check, not a probe. It can only see that this actor holds no handle, never that a handle is present but dead server-side, because
ISubscriptionHandleis opaque. That remains option B, deferred exactly as suggested until a real occurrence shows the handle outliving the subscription.A redundant
Subscribeis possible and is accepted. A tick queued ahead of an already-queuedSubscribeobserves the same no-handle state and tells a second one. Harmless:HandleSubscribeAsyncis aReceiveAsync(so no tick is processed mid-subscribe) and it drops any prior subscription before establishing the new one, so it costs one extra round-trip and converges. Suppressing it would need a pending-subscribe flag threaded through every self-tell site — more state than the race is worth.healthPollIntervalbecame an optionalPropsparameter, mirroring the existingrediscoverIntervalseam, so a test can drive the reconcile without waiting 30 s.Three tests. The reconcile is proven by a stub whose first subscribe throws: nothing else would ever retry it — no reconnect, no deploy — so a second attempt can only come from the reconcile. Disabling
ReconcileSubscriptionturns it red (verified). The two guard tests are absence assertions bounded by a positive control, not a sleep: a countingIDriverHealthPublishermakes ticks processed observable, so "still one subscribe after ≥4 ticks" means the reconcile ran and declined, not that the timer had not fired. Runtime.Tests 507/507.Not deploy-time drift (#486) — kept separate as the issue asks.
dohertj2 referenced this issue2026-10-09 07:41:03 -04:00