Files

57 lines
2.0 KiB
C#

using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;
public sealed class GatewayStaleDetectorService : BackgroundService
{
private readonly IServiceScopeFactory _scopes;
private readonly GatewayMonitoringOptions _opts;
private readonly ClinicalMetrics _metrics;
private readonly ILogger<GatewayStaleDetectorService> _logger;
public GatewayStaleDetectorService(
IServiceScopeFactory scopes,
IOptions<GatewayMonitoringOptions> opts,
ClinicalMetrics metrics,
ILogger<GatewayStaleDetectorService> logger)
{
_scopes = scopes;
_opts = opts.Value;
_metrics = metrics;
_logger = logger;
}
protected override async Task ExecuteAsync(CancellationToken ct)
{
using var timer = new PeriodicTimer(TimeSpan.FromMinutes(_opts.PollIntervalMinutes));
while (await timer.WaitForNextTickAsync(ct))
await DetectStaleAsync(ct);
}
private async Task DetectStaleAsync(CancellationToken ct)
{
await using var scope = _scopes.CreateAsyncScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
var cutoff = DateTimeOffset.UtcNow.AddMinutes(-_opts.StaleThresholdMinutes);
var stale = await db.WardGateways
.Include(g => g.Site)
.Where(g => g.Status != GatewayStatus.Offline
&& (g.LastHeartbeatAt == null || g.LastHeartbeatAt < cutoff))
.ToListAsync(ct);
foreach (var gateway in stale)
{
gateway.MarkOffline();
_logger.LogWarning(
"Gateway {Code} ({Department}) marked OFFLINE — last heartbeat {LastHeartbeat}",
gateway.GatewayCode, gateway.Department, gateway.LastHeartbeatAt);
_metrics.WardGatewaysOffline
.WithLabels(gateway.Site.SiteCode)
.Inc();
}
if (stale.Count > 0)
await db.SaveChangesAsync(ct);
}
}