use crate::http_transport::ConnectionGuard;
use std::{
sync::{
Arc, Mutex,
atomic::{AtomicUsize, Ordering},
},
time::Duration,
};
use tokio::time::Instant;
use tokio_util::task::TaskTracker;
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum AgentHttpServerStatus {
Ready,
Unavailable,
Draining,
Stopped,
}
struct Record {
status: AgentHttpServerStatus,
success: Option<Instant>,
}
#[derive(Clone)]
pub struct AgentHttpServerHealth {
record: Arc<Mutex<Record>>,
active: Arc<AtomicUsize>,
freshness: Duration,
streams: TaskTracker,
}
impl AgentHttpServerHealth {
pub(super) fn new(freshness: Duration, streams: TaskTracker) -> Self {
Self {
record: Arc::new(Mutex::new(Record {
status: AgentHttpServerStatus::Unavailable,
success: None,
})),
active: Arc::new(AtomicUsize::new(0)),
freshness,
streams,
}
}
#[must_use]
pub fn status(&self) -> AgentHttpServerStatus {
let record = self
.record
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if record.status == AgentHttpServerStatus::Ready
&& record
.success
.is_none_or(|time| time.elapsed() >= self.freshness)
{
AgentHttpServerStatus::Unavailable
} else {
record.status
}
}
#[must_use]
pub fn is_ready(&self) -> bool {
self.status() == AgentHttpServerStatus::Ready
}
#[must_use]
pub fn is_live(&self) -> bool {
self.status() != AgentHttpServerStatus::Stopped
}
#[must_use]
pub fn active_connections(&self) -> usize {
self.active.load(Ordering::Acquire)
}
#[must_use]
pub fn active_streams(&self) -> usize {
self.streams.len()
}
pub(super) fn connection(&self) -> ConnectionGuard {
ConnectionGuard::new(self.active.clone())
}
pub(super) fn update(&self, status: AgentHttpServerStatus) {
let mut record = self
.record
.lock()
.unwrap_or_else(std::sync::PoisonError::into_inner);
if record.status == AgentHttpServerStatus::Stopped
|| (record.status == AgentHttpServerStatus::Draining
&& status != AgentHttpServerStatus::Stopped)
{
return;
}
record.status = status;
record.success = (status == AgentHttpServerStatus::Ready).then(Instant::now);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stale_evidence_and_shutdown_cannot_report_ready() {
let health = AgentHttpServerHealth::new(Duration::from_secs(1), TaskTracker::new());
health.update(AgentHttpServerStatus::Ready);
assert!(health.is_ready());
health.record.lock().unwrap().success = Some(Instant::now() - Duration::from_secs(2));
assert!(!health.is_ready());
assert!(health.is_live());
health.update(AgentHttpServerStatus::Draining);
health.update(AgentHttpServerStatus::Ready);
assert_eq!(health.status(), AgentHttpServerStatus::Draining);
health.update(AgentHttpServerStatus::Stopped);
health.update(AgentHttpServerStatus::Ready);
assert!(!health.is_live());
}
}