use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum HealthStatus {
Healthy,
Degraded,
Unhealthy,
}
#[derive(Debug, Clone)]
pub struct HealthCheckResult {
pub status: HealthStatus,
pub message: String,
pub timestamp: Instant,
pub metadata: Vec<(String, String)>,
}
impl HealthCheckResult {
pub fn healthy(message: impl Into<String>) -> Self {
Self {
status: HealthStatus::Healthy,
message: message.into(),
timestamp: Instant::now(),
metadata: Vec::new(),
}
}
pub fn degraded(message: impl Into<String>) -> Self {
Self {
status: HealthStatus::Degraded,
message: message.into(),
timestamp: Instant::now(),
metadata: Vec::new(),
}
}
pub fn unhealthy(message: impl Into<String>) -> Self {
Self {
status: HealthStatus::Unhealthy,
message: message.into(),
timestamp: Instant::now(),
metadata: Vec::new(),
}
}
pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
self.metadata.push((key.into(), value.into()));
self
}
}
#[derive(Clone)]
pub struct WorkerHealthChecker {
last_heartbeat: Arc<Mutex<Instant>>,
last_task_processed: Arc<Mutex<Option<Instant>>>,
heartbeat_timeout: Duration,
task_timeout: Duration,
}
impl WorkerHealthChecker {
pub fn new(heartbeat_timeout: Duration, task_timeout: Duration) -> Self {
Self {
last_heartbeat: Arc::new(Mutex::new(Instant::now())),
last_task_processed: Arc::new(Mutex::new(None)),
heartbeat_timeout,
task_timeout,
}
}
pub fn heartbeat(&self) {
*self
.last_heartbeat
.lock()
.expect("lock should not be poisoned") = Instant::now();
}
pub fn task_processed(&self) {
*self
.last_task_processed
.lock()
.expect("lock should not be poisoned") = Some(Instant::now());
}
pub fn check_health(&self) -> HealthCheckResult {
let now = Instant::now();
let last_heartbeat = *self
.last_heartbeat
.lock()
.expect("lock should not be poisoned");
let last_task = *self
.last_task_processed
.lock()
.expect("lock should not be poisoned");
if now.duration_since(last_heartbeat) > self.heartbeat_timeout {
return HealthCheckResult::unhealthy("Worker heartbeat timeout").with_metadata(
"last_heartbeat_seconds_ago",
format!("{}", now.duration_since(last_heartbeat).as_secs()),
);
}
if let Some(last_task_time) = last_task {
if now.duration_since(last_task_time) > self.task_timeout {
return HealthCheckResult::degraded("No tasks processed recently").with_metadata(
"last_task_seconds_ago",
format!("{}", now.duration_since(last_task_time).as_secs()),
);
}
}
HealthCheckResult::healthy("Worker is operational").with_metadata(
"uptime_seconds",
format!("{}", now.duration_since(last_heartbeat).as_secs()),
)
}
pub fn is_ready(&self) -> bool {
matches!(
self.check_health().status,
HealthStatus::Healthy | HealthStatus::Degraded
)
}
pub fn is_alive(&self) -> bool {
let now = Instant::now();
let last_heartbeat = *self
.last_heartbeat
.lock()
.expect("lock should not be poisoned");
now.duration_since(last_heartbeat) <= self.heartbeat_timeout
}
}
impl Default for WorkerHealthChecker {
fn default() -> Self {
Self::new(Duration::from_secs(30), Duration::from_secs(300))
}
}
pub struct DependencyChecker {
name: String,
check_fn: Box<dyn Fn() -> HealthCheckResult + Send + Sync>,
}
impl DependencyChecker {
pub fn new<F>(name: impl Into<String>, check_fn: F) -> Self
where
F: Fn() -> HealthCheckResult + Send + Sync + 'static,
{
Self {
name: name.into(),
check_fn: Box::new(check_fn),
}
}
pub fn check(&self) -> HealthCheckResult {
(self.check_fn)()
}
pub fn name(&self) -> &str {
&self.name
}
}