using System.Collections.Concurrent; using ZB.MOM.WW.OtOpcUa.Core.Abstractions; namespace ZB.MOM.WW.OtOpcUa.Runtime.Drivers; /// The last health snapshot a driver child published, as kept by . /// The driver's reported state. /// When the driver last read successfully, if ever. /// The driver's last error, if any. /// Per-host connectivity for a multi-host driver, or null when the driver has none. /// When the snapshot was published — the child's dedup means this is the last CHANGE. public sealed record ChildHealthRecord( DriverState State, DateTime? LastSuccessfulReadUtc, string? LastError, IReadOnlyList? HostStatuses, DateTime PublishedUtc); /// /// An decorator that remembers the last snapshot per driver instance /// and forwards every publish to the real publisher. wraps the publisher it /// hands its children in one of these so GetDiagnostics can answer from the children's own health /// (state, host counts, last change) instead of a placeholder — the children publish on their own actor /// threads, hence the concurrent map. /// /// /// Deliberately NOT a message to the host: the children already publish through this seam on every /// change, so a parallel Tell would double every health transition's traffic for one consumer. /// Entries are removed by the host when it stops a child; a late publish from a stopping child may re-add /// one, which the host filters against its live child set when it answers. /// public sealed class ChildHealthLedger : IDriverHealthPublisher { private readonly IDriverHealthPublisher _inner; private readonly ConcurrentDictionary _last = new(StringComparer.Ordinal); /// Wraps . /// The publisher every snapshot is forwarded to. public ChildHealthLedger(IDriverHealthPublisher inner) { ArgumentNullException.ThrowIfNull(inner); _inner = inner; } /// public void Publish( string clusterId, string driverInstanceId, DriverHealth health, int errorCount5Min, DateTime? rediscoveryNeededUtc = null, string? rediscoveryReason = null, IReadOnlyList? hostStatuses = null, SkippedTagReport? skippedTags = null, IReadOnlyDictionary? diagnostics = null) { _last[driverInstanceId] = new ChildHealthRecord( health.State, health.LastSuccessfulRead, health.LastError, hostStatuses, DateTime.UtcNow); _inner.Publish( clusterId, driverInstanceId, health, errorCount5Min, rediscoveryNeededUtc, rediscoveryReason, hostStatuses, skippedTags, diagnostics); } /// The last snapshot published for , or null if none yet. /// The driver instance. /// The record, or null. public ChildHealthRecord? TryGet(string driverInstanceId) => _last.TryGetValue(driverInstanceId, out var record) ? record : null; /// Forgets 's snapshot (the child was stopped). /// The driver instance. public void Forget(string driverInstanceId) => _last.TryRemove(driverInstanceId, out _); }