fix(historian): probe the gateway on a timer for the health snapshot (lmxopcua#746)

The Gateway historian's ProcessConnectionOpen / EventConnectionOpen were
derived from read outcomes only, so a freshly booted node with a healthy
gateway reported the historian closed until a client read, and a node that
only writes (continuous historization) never flipped it.

The data source now runs a ProbeAsync loop (ServerHistorian:Gateway:
ProbeInterval, default 30 s, first probe at start-up; 0 = off). Each probe,
bounded at 10 s, is recorded as the latest connectivity observation; the
latest observation (probe or read) is what the snapshot reports. Probes never
touch the query/failure counters. DisposeAsync stops the loop before
disposing the client.

Rebased from the unpushed remed/lane-H2 branch (originally authored 2026-10-22
in session_01Ub9hGmYLQvn9XETzw7CMsT) onto current master.

Claude-Session: https://claude.ai/code/session_01GT67AEHd2Acv1YPwGfXEoV
This commit is contained in:
Joseph Doherty
2026-10-22 16:33:11 -04:00
parent 0b85c499e3
commit 8dc594d8e4
5 changed files with 233 additions and 10 deletions
@@ -291,4 +291,13 @@ public sealed class GatewayHistorianSettings
/// time-weighted aggregates on this backend). Turn it on only once a fixed gateway is live-verified.
/// </summary>
public bool EnableNativeTimeWeighted { get; init; }
/// <summary>
/// How often the read client probes the gateway (<c>ProbeAsync</c>) for the historian health snapshot's
/// connection flags. The first probe runs at start-up, so a node that has served no HistoryRead yet — or only
/// writes (continuous historization) — reports a healthy gateway as open instead of closed. The latest
/// observation (probe or read) wins. Default 30 s; zero or negative = no probe (health from read outcomes only,
/// the previous behaviour).
/// </summary>
public TimeSpan ProbeInterval { get; init; } = TimeSpan.FromSeconds(30);
}