use std::sync::Arc;
use std::time::Duration;
use tokio_util::sync::CancellationToken;
use crate::proxy::upstream::{GenericRestAdapter, HealthTracker, UpstreamStatus};
#[derive(Debug, Clone)]
pub struct HealthCheckConfig {
pub check_interval: Duration,
pub recovery_check_interval: Duration,
pub check_timeout: Duration,
}
impl Default for HealthCheckConfig {
fn default() -> Self {
Self {
check_interval: Duration::from_secs(30),
recovery_check_interval: Duration::from_secs(5),
check_timeout: Duration::from_secs(5),
}
}
}
pub struct HealthCheckWorker {
upstream: Arc<GenericRestAdapter>,
tracker: Arc<HealthTracker>,
config: HealthCheckConfig,
cancel: CancellationToken,
}
impl HealthCheckWorker {
pub fn new(
upstream: Arc<GenericRestAdapter>,
tracker: Arc<HealthTracker>,
config: HealthCheckConfig,
cancel: CancellationToken,
) -> Self {
Self {
upstream,
tracker,
config,
cancel,
}
}
pub fn with_defaults(
upstream: Arc<GenericRestAdapter>,
tracker: Arc<HealthTracker>,
cancel: CancellationToken,
) -> Self {
Self::new(upstream, tracker, HealthCheckConfig::default(), cancel)
}
pub async fn run(&self) {
loop {
let interval = match self.tracker.status() {
UpstreamStatus::Online => self.config.check_interval,
#[allow(clippy::arithmetic_side_effects)]
UpstreamStatus::Degraded => self.config.check_interval / 2,
UpstreamStatus::Offline => self.config.recovery_check_interval,
};
tokio::select! {
_ = self.cancel.cancelled() => {
break;
}
_ = tokio::time::sleep(interval) => {
self.perform_health_check().await;
}
}
}
}
async fn perform_health_check(&self) {
match self.upstream.health_check().await {
Ok(true) => {
self.tracker.record_success();
}
Ok(false) | Err(_) => {
self.tracker.record_failure();
}
}
}
pub fn status(&self) -> UpstreamStatus {
self.tracker.status()
}
pub fn is_running(&self) -> bool {
!self.cancel.is_cancelled()
}
}
#[cfg(test)]
#[path = "tests/health_tests.rs"]
mod tests;