57 lines
2.0 KiB
C#
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);
|
|
}
|
|
} |