From 953795462620427e4c8d7654d7ee2e9fa6b3e48e Mon Sep 17 00:00:00 2001 From: TekH Date: Mon, 13 Jul 2026 14:03:49 +0200 Subject: [PATCH] Improve ProfileWorker health-check logic and initialization Refactor the `ProfileWorker` class to enhance health-check handling, including support for an uninitialized state. The `_lastSuccessfulRun` field was changed to a nullable `DateTime?` to represent when the service has not yet completed its first successful run. Introduce a new health state, **Degraded (Initializing)**, to indicate the service is starting up. Update the `CheckHealthAsync` method to handle this state and return a `HealthCheckResult.Degraded` with relevant metadata. Refine health-check logic to safely access `_lastSuccessfulRun.Value` only when initialized. Update documentation to clarify health status conditions and retain adaptive multiplier logic for detecting issues based on interval length. Enhance `HealthCheckResult` metadata with additional details, including `LastSuccessfulRun`, time since last success, and consecutive failures. Improve comments and descriptions for better clarity. --- .../ProfileWorker/ProfileWorker.cs | 41 +++++++++++++++---- 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/ECMJobRunner.WebCron/ProfileWorker/ProfileWorker.cs b/ECMJobRunner.WebCron/ProfileWorker/ProfileWorker.cs index f6b2d0f..6a1f054 100644 --- a/ECMJobRunner.WebCron/ProfileWorker/ProfileWorker.cs +++ b/ECMJobRunner.WebCron/ProfileWorker/ProfileWorker.cs @@ -20,6 +20,7 @@ namespace ECMJobRunner.WebCron.ProfileWorker; /// /// Health states (with adaptive thresholds): /// +/// Degraded (Initializing): Service starting up, waiting for first successful run /// Healthy: Recent successful run with no failures /// Degraded: 1+ consecutive failures but within time threshold /// Unhealthy: No success within adaptive threshold (3x interval for fast intervals <10s, 1.5x for slower intervals) @@ -35,7 +36,7 @@ public class ProfileWorker( private readonly ProfileWorkerOptions _options = Options.Value; // Health check state - private DateTime _lastSuccessfulRun = DateTime.UtcNow; + private DateTime? _lastSuccessfulRun = null; // null = not run yet private int _consecutiveFailures = 0; private Exception? _lastException = null; @@ -107,13 +108,14 @@ public class ProfileWorker( /// A indicating the current health status: /// /// - Service running normally - /// - Recent failures but still operational + /// - Recent failures but still operational, or service initializing /// - No successful run for extended period /// /// /// /// Health determination logic: /// + /// Degraded (Initializing): Service has not completed its first successful run yet /// Unhealthy: Time since last success exceeds adaptive threshold (3x interval for fast intervals <10s, 1.5x for slower intervals) /// Degraded: 1+ consecutive failures within time threshold /// Healthy: Recent successful run with no failures @@ -124,7 +126,32 @@ public class ProfileWorker( HealthCheckContext context, CancellationToken cancellationToken = default) { - var timeSinceLastSuccess = DateTime.UtcNow - _lastSuccessfulRun; + // Service hasn't completed its first successful run yet + if (_lastSuccessfulRun == null) + { + var data = new Dictionary + { + ["Status"] = "Initializing", + ["ConsecutiveFailures"] = _consecutiveFailures, + ["IntervalMS"] = _options.IntervalMS + }; + + if (_lastException != null) + { + data["LastException"] = _lastException.Message; + } + + // Degraded during initialization (not unhealthy, service is starting up) + return Task.FromResult(HealthCheckResult.Degraded( + _consecutiveFailures > 0 + ? $"ProfileWorker initializing with {_consecutiveFailures} failure(s)" + : "ProfileWorker is initializing, waiting for first successful run", + _lastException, + data + )); + } + + var timeSinceLastSuccess = DateTime.UtcNow - _lastSuccessfulRun.Value; // Adaptive multiplier: 3x for fast intervals (<10s), 1.5x for slower intervals // This ensures faster problem detection when using longer intervals (e.g., 60s) @@ -136,7 +163,7 @@ public class ProfileWorker( { var data = new Dictionary { - ["LastSuccessfulRun"] = _lastSuccessfulRun, + ["LastSuccessfulRun"] = _lastSuccessfulRun.Value, ["TimeSinceLastSuccess"] = timeSinceLastSuccess, ["ConsecutiveFailures"] = _consecutiveFailures, ["IntervalMS"] = _options.IntervalMS, @@ -156,12 +183,12 @@ public class ProfileWorker( )); } - // Degraded: 1-2 consecutive failures but within time limit + // Degraded: 1+ consecutive failures but within time limit if (_consecutiveFailures > 0) { var data = new Dictionary { - ["LastSuccessfulRun"] = _lastSuccessfulRun, + ["LastSuccessfulRun"] = _lastSuccessfulRun.Value, ["ConsecutiveFailures"] = _consecutiveFailures }; @@ -177,7 +204,7 @@ public class ProfileWorker( "ProfileWorker is running normally", new Dictionary { - ["LastSuccessfulRun"] = _lastSuccessfulRun + ["LastSuccessfulRun"] = _lastSuccessfulRun.Value } )); }