use std::io::Write;
use std::net::{Shutdown, SocketAddr, TcpListener, TcpStream};
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex, OnceLock};
use std::thread::{self, JoinHandle};
use std::time::Duration;
use crate::ServerError;
use crate::server::listener::{
acquire_reserve_descriptor, loopback_interrupt_target, prepare_accepted_socket,
shed_on_fd_exhaustion,
};
use super::checks::{SharedReadinessState, health_check, readiness_check};
use super::reissue::{
OperatorCredentialReissueOutcome, OperatorCredentialReissueRefusal,
OperatorCredentialReissueRequest, OperatorCredentialReissuer, SharedOperatorCredentialReissue,
};
use super::unloadable::{SharedUnloadableConversations, UnloadableConversationRecord};
use super::metrics_route;
const HEALTH_PATH: &str = "/health";
const READY_PATH: &str = "/ready";
const METRICS_PATH: &str = "/metrics";
const UNLOADABLE_PATH: &str = "/unloadable-conversations";
const REISSUE_PATH: &str = "/operator/credential-reissue";
const RECLAIM_PATH: &str = "/reclaim";
const QUIESCE_PATH: &str = "/operator/reclaim-quiesce";
const APPLICATION_JSON: &str = "application/json";
const READ_BUFFER_BYTES: usize = 2048;
#[derive(Debug)]
pub struct HealthServerHandle {
drafts: admin::DraftRoute,
grant_stream: Arc<OnceLock<Arc<crate::auth_membership::grant_stream::StreamState>>>,
lys_root: Arc<OnceLock<crate::auth_lys::root_presence_start::RootSlot>>,
local_addr: SocketAddr,
interrupt_target: SocketAddr,
shutdown: Arc<AtomicBool>,
active_stream: Arc<Mutex<Option<TcpStream>>>,
unloadable: SharedUnloadableConversations,
reissue: SharedOperatorCredentialReissue,
reclaim: super::reclaim_route::SharedReclaimSpine,
ephemeral_sweep: Arc<OnceLock<liminal::durability::EphemeralSweepCounts>>,
listen_addresses: Arc<OnceLock<Vec<SocketAddr>>>,
link_bridges: crate::link_bridge::BridgeRegistry,
worker: Option<JoinHandle<Result<(), ServerError>>>,
#[cfg(test)]
accept_attempts: Arc<AtomicU64>,
#[cfg(test)]
shed_count: Arc<AtomicU64>,
#[cfg(test)]
requests_entered: Arc<AtomicU64>,
}
impl HealthServerHandle {
pub fn install_grant_stream(
&self,
state: Arc<crate::auth_membership::grant_stream::StreamState>,
) -> Result<(), ServerError> {
self.grant_stream
.set(state)
.map_err(|_| ServerError::HealthEndpoint {
message: "grant_stream_owner_already_installed".into(),
})
}
pub fn install_lys_root(
&self,
root: crate::auth_lys::root_presence_start::RootSlot,
) -> Result<(), ServerError> {
self.lys_root
.set(root)
.map_err(|_| ServerError::HealthEndpoint {
message: "lys_root_already_installed".into(),
})
}
pub fn install_listen_addresses(&self, addresses: Vec<SocketAddr>) -> Result<(), ServerError> {
self.listen_addresses
.set(addresses)
.map_err(|_| ServerError::HealthEndpoint {
message: "wire listen addresses are already installed".to_owned(),
})
}
#[must_use]
pub fn link_bridges(&self) -> crate::link_bridge::BridgeRegistry {
self.link_bridges.clone()
}
#[must_use]
pub const fn local_addr(&self) -> SocketAddr {
self.local_addr
}
pub fn install_spine(&self, spine: &Arc<haematite::EventStore>) {
if !self.reclaim.install(spine) {
tracing::warn!(
"the spine was installed on the health endpoint already; the first stands"
);
}
}
pub fn install_unloadable_record(&self, record: UnloadableConversationRecord) {
self.unloadable.install(record);
}
pub fn install_credential_reissuer(&self, reissuer: Arc<dyn OperatorCredentialReissuer>) {
self.reissue.install(reissuer);
}
pub fn install_ephemeral_sweep_report(
&self,
report: liminal::durability::EphemeralSweepCounts,
) -> Result<(), ServerError> {
self.ephemeral_sweep
.set(report)
.map_err(|_| ServerError::HealthEndpoint {
message: "ephemeral sweep report is already installed".to_owned(),
})
}
pub fn shutdown(mut self) -> Result<(), ServerError> {
self.stop_worker()
}
fn stop_worker(&mut self) -> Result<(), ServerError> {
self.shutdown.store(true, Ordering::SeqCst);
let Some(worker) = self.worker.take() else {
return Ok(());
};
let in_flight = self
.active_stream
.lock()
.map_or(None, |mut slot| slot.take());
if let Some(stream) = in_flight
&& let Err(error) = stream.shutdown(Shutdown::Both)
{
tracing::debug!(%error, "health endpoint in-flight stream already closed at shutdown");
}
match TcpStream::connect(self.interrupt_target) {
Ok(waker) => drop(waker),
Err(error) => tracing::warn!(
interrupt_target = %self.interrupt_target,
%error,
"health endpoint accept interrupt could not connect; the accept \
worker will only stop if it has already exited"
),
}
worker.join().map_err(|_| ServerError::HealthEndpoint {
message: "health endpoint worker thread terminated unexpectedly".to_owned(),
})?
}
#[cfg(test)]
fn accept_attempts(&self) -> u64 {
self.accept_attempts.load(Ordering::SeqCst)
}
#[cfg(test)]
fn shed_count(&self) -> u64 {
self.shed_count.load(Ordering::SeqCst)
}
#[cfg(test)]
fn requests_entered(&self) -> u64 {
self.requests_entered.load(Ordering::SeqCst)
}
}
impl Drop for HealthServerHandle {
fn drop(&mut self) {
if let Err(error) = self.stop_worker() {
tracing::debug!(%error, "health endpoint shutdown during drop failed");
}
}
}
pub fn start_health_server(
bind_address: SocketAddr,
readiness: SharedReadinessState,
) -> Result<HealthServerHandle, ServerError> {
let started_at = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.map_err(|error| ServerError::HealthEndpoint {
message: format!("startup clock: {error}"),
})?;
let started_at =
u64::try_from(started_at.as_millis()).map_err(|error| ServerError::HealthEndpoint {
message: format!("startup clock: {error}"),
})?;
let listener =
TcpListener::bind(bind_address).map_err(|error| ServerError::HealthEndpoint {
message: format!("failed to bind health endpoint at {bind_address}: {error}"),
})?;
let local_addr = listener
.local_addr()
.map_err(|error| ServerError::HealthEndpoint {
message: format!("failed to inspect health endpoint listener address: {error}"),
})?;
let interrupt_target = loopback_interrupt_target(local_addr);
let shutdown = Arc::new(AtomicBool::new(false));
let active_stream = Arc::new(Mutex::new(None));
let unloadable = SharedUnloadableConversations::default();
let reissue = SharedOperatorCredentialReissue::default();
let reclaim = super::reclaim_route::SharedReclaimSpine::default();
let ephemeral_sweep = Arc::new(OnceLock::new());
let listen_addresses = Arc::new(OnceLock::new());
let link_bridges = crate::link_bridge::BridgeRegistry::default();
let grant_stream = Arc::new(OnceLock::new());
let lys_root = Arc::new(OnceLock::new());
let drafts = Arc::new(Mutex::new(None));
let accept_attempts = Arc::new(AtomicU64::new(0));
let shed_count = Arc::new(AtomicU64::new(0));
let requests_entered = Arc::new(AtomicU64::new(0));
let worker_shutdown = Arc::clone(&shutdown);
let worker_stream = Arc::clone(&active_stream);
let worker_attempts = Arc::clone(&accept_attempts);
let worker_shed = Arc::clone(&shed_count);
let worker_entered = Arc::clone(&requests_entered);
let served = ServedState {
started_at,
drafts: drafts.clone(),
grant_stream: Arc::clone(&grant_stream),
lys_root: Arc::clone(&lys_root),
readiness,
unloadable: unloadable.clone(),
reissue: reissue.clone(),
reclaim: reclaim.clone(),
ephemeral_sweep: Arc::clone(&ephemeral_sweep),
listen_addresses: Arc::clone(&listen_addresses),
link_bridges: link_bridges.clone(),
};
let worker = thread::spawn(move || {
serve(
&listener,
&served,
&worker_shutdown,
&worker_stream,
&worker_attempts,
&worker_shed,
&worker_entered,
)
});
Ok(HealthServerHandle {
drafts,
grant_stream,
lys_root,
local_addr,
interrupt_target,
shutdown,
active_stream,
unloadable,
reissue,
reclaim,
ephemeral_sweep,
listen_addresses,
link_bridges,
worker: Some(worker),
#[cfg(test)]
accept_attempts,
#[cfg(test)]
shed_count,
#[cfg(test)]
requests_entered,
})
}
#[derive(Debug, Clone)]
struct ServedState {
started_at: u64,
drafts: admin::DraftRoute,
grant_stream: Arc<OnceLock<Arc<crate::auth_membership::grant_stream::StreamState>>>,
lys_root: Arc<OnceLock<crate::auth_lys::root_presence_start::RootSlot>>,
readiness: SharedReadinessState,
unloadable: SharedUnloadableConversations,
reissue: SharedOperatorCredentialReissue,
reclaim: super::reclaim_route::SharedReclaimSpine,
ephemeral_sweep: Arc<OnceLock<liminal::durability::EphemeralSweepCounts>>,
listen_addresses: Arc<OnceLock<Vec<SocketAddr>>>,
link_bridges: crate::link_bridge::BridgeRegistry,
}
#[derive(serde::Serialize)]
struct HealthWithLinks {
started_at: u64,
grant_stream: Option<crate::auth_membership::grant_stream::StreamHealth>,
lys_root: Option<&'static str>,
#[serde(skip_serializing_if = "Option::is_none")]
lys_root_cause: Option<&'static str>,
#[serde(flatten, serialize_with = "health_with_lys_asks")]
health: super::checks::HealthStatus,
links: std::collections::BTreeMap<String, crate::link_bridge::BridgeCounters>,
listen_addresses: Vec<SocketAddr>,
}
fn health_with_lys_asks<S: serde::Serializer>(
health: &super::checks::HealthStatus,
serializer: S,
) -> Result<S::Ok, S::Error> {
#[derive(serde::Serialize)]
struct WithAsks<'a> {
#[serde(flatten)]
health: &'a super::checks::HealthStatus,
lys_ask: crate::auth_lys::live::AskStatistics,
}
let lys_ask = crate::auth_lys::live::ask_statistics().map_err(serde::ser::Error::custom)?;
serde::Serialize::serialize(&WithAsks { health, lys_ask }, serializer)
}
#[derive(serde::Serialize)]
struct ReadyWithStream {
ready: bool,
unmet_conditions: Vec<ReadyCondition>,
grant_stream: Option<crate::auth_membership::grant_stream::StreamHealth>,
}
#[derive(serde::Serialize)]
#[serde(untagged)]
enum ReadyCondition {
Existing(super::checks::ReadinessCondition),
Stream(&'static str),
}
mod responses;
mod routes;
mod serve;
use responses::{StatusCode, empty_response, json_response, parse_request_line, response};
use routes::response_for_request;
use serve::serve;
#[cfg(test)]
#[path = "endpoint/tests.rs"]
mod tests;
#[cfg(test)]
mod lys_ask_health_tests {
use super::*;
#[test]
fn health_reports_lys_ask_count_and_running_maximum() -> Result<(), Box<dyn std::error::Error>>
{
let health = HealthWithLinks {
started_at: 1,
grant_stream: None,
lys_root: None,
lys_root_cause: None,
health: health_check(),
links: std::collections::BTreeMap::new(),
listen_addresses: Vec::new(),
};
let value = serde_json::to_value(health)?;
assert!(value["lys_ask"]["count"].is_u64());
assert!(value["lys_ask"]["max_elapsed_ms"].is_f64());
assert_eq!(value["status"], "healthy");
assert!(value["grant_stream"].is_null());
Ok(())
}
}
pub(crate) mod admin;