using Akka.Actor; using Akka.Event; using Microsoft.EntityFrameworkCore; using ZB.MOM.WW.OtOpcUa.Configuration; namespace ZB.MOM.WW.OtOpcUa.Runtime.Health; /// /// Single-flight cached health probe against the ConfigDb. Reads cached state via /// Ask<DbHealthStatus>; a single SELECT 1 runs at most every RefreshInterval. /// Consumed by both the host's /health/ready endpoint and /// RedundancyStateActor's stale calc. /// public sealed class DbHealthProbeActor : ReceiveActor, IWithTimers { public static readonly TimeSpan RefreshInterval = TimeSpan.FromSeconds(5); public sealed class GetStatus { public static readonly GetStatus Instance = new(); private GetStatus() { } } public sealed record DbHealthStatus(bool Reachable, DateTime AsOfUtc, string? LastError); public sealed class Tick { public static readonly Tick Instance = new(); private Tick() { } } private readonly IDbContextFactory _dbFactory; private readonly ILoggingAdapter _log = Context.GetLogger(); private DbHealthStatus _last = new(false, DateTime.MinValue, "not probed yet"); /// Gets or sets the timer scheduler for periodic health probes. public ITimerScheduler Timers { get; set; } = null!; /// Creates a Props instance for the DbHealthProbeActor. /// The factory for creating ConfigDb contexts. /// The Akka for creating this actor. public static Props Props(IDbContextFactory dbFactory) => Akka.Actor.Props.Create(() => new DbHealthProbeActor(dbFactory)); /// Initializes a new instance of the class. /// The factory for creating ConfigDb contexts. public DbHealthProbeActor(IDbContextFactory dbFactory) { _dbFactory = dbFactory; Receive(_ => Sender.Tell(_last)); Receive(_ => RunProbe()); } /// protected override void PreStart() { RunProbe(); Timers.StartPeriodicTimer("probe", Tick.Instance, RefreshInterval); } private void RunProbe() { try { using var db = _dbFactory.CreateDbContext(); _ = db.Deployments.AsNoTracking().Take(1).ToList(); _last = new DbHealthStatus(true, DateTime.UtcNow, null); } catch (Exception ex) { _last = new DbHealthStatus(false, DateTime.UtcNow, ex.Message); _log.Warning(ex, "DbHealthProbe: probe failed"); } } }