#![allow(dead_code)]
use std::time::{Duration, Instant};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ChildHealth {
pub exists: bool,
pub zombie: bool,
pub state_hint: Option<char>,
pub counters_trusted: bool,
pub cpu_time_total: u64,
pub thread_count: Option<u32>,
pub voluntary_ctxt: Option<u64>,
pub sample_time: Instant,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SilenceHealthOutcome {
ChildNotRunning,
ChildZombie,
StillBusyExtendWait,
AppearsHung,
}
#[must_use]
pub fn sample_child_health(pid: u32) -> ChildHealth {
if pid == 0 {
return ChildHealth::cannot_sample();
}
#[cfg(target_os = "linux")]
{
linux::sample_child_health_linux(pid)
}
#[cfg(target_os = "macos")]
{
macos::sample_child_health_macos(pid)
}
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
{
other::sample_child_health_other(pid)
}
}
#[must_use]
pub fn silence_grace_for_rpc_timeout(rpc_timeout: Duration) -> Duration {
const MIN: Duration = Duration::from_millis(50);
const MAX: Duration = Duration::from_millis(250);
let q = rpc_timeout / 8;
if q < MIN {
MIN
} else if q > MAX {
MAX
} else {
q
}
}
#[must_use]
pub const fn health_indicates_progress(before: &ChildHealth, after: &ChildHealth) -> bool {
if !after.counters_trusted {
return false;
}
if !before.counters_trusted {
return false;
}
if before.cpu_time_total != after.cpu_time_total {
return true;
}
if let (Some(a), Some(b)) = (before.voluntary_ctxt, after.voluntary_ctxt)
&& a != b {
return true;
}
if let (Some(a), Some(b)) = (before.thread_count, after.thread_count)
&& a != b {
return true;
}
false
}
const fn silence_outcome_from_pair(
first: &ChildHealth,
second: &ChildHealth,
) -> SilenceHealthOutcome {
if !first.exists {
return SilenceHealthOutcome::ChildNotRunning;
}
if first.zombie {
return SilenceHealthOutcome::ChildZombie;
}
if !second.exists {
return SilenceHealthOutcome::ChildNotRunning;
}
if second.zombie {
return SilenceHealthOutcome::ChildZombie;
}
if health_indicates_progress(first, second) {
SilenceHealthOutcome::StillBusyExtendWait
} else {
SilenceHealthOutcome::AppearsHung
}
}
pub async fn evaluate_after_acp_silence(pid: u32, grace: Duration) -> SilenceHealthOutcome {
let first = sample_child_health(pid);
if !first.exists {
return SilenceHealthOutcome::ChildNotRunning;
}
if first.zombie {
return SilenceHealthOutcome::ChildZombie;
}
tokio::time::sleep(grace).await;
let second = sample_child_health(pid);
silence_outcome_from_pair(&first, &second)
}
#[cfg(target_os = "linux")]
mod linux;
#[cfg(target_os = "macos")]
mod macos;
#[cfg(not(any(target_os = "linux", target_os = "macos")))]
pub(crate) mod other;
#[cfg(test)]
#[path = "tests/child_health_tests_root.rs"]
mod child_health_unit_tests;
impl ChildHealth {
pub(super) fn process_absent() -> Self {
Self {
exists: false,
zombie: false,
state_hint: None,
counters_trusted: true,
cpu_time_total: 0,
thread_count: None,
voluntary_ctxt: None,
sample_time: Instant::now(),
}
}
pub(super) fn cannot_sample() -> Self {
Self {
exists: true,
zombie: false,
state_hint: None,
counters_trusted: false,
cpu_time_total: 0,
thread_count: None,
voluntary_ctxt: None,
sample_time: Instant::now(),
}
}
}
#[cfg(test)]
mod child_health_smoke {
#[test]
fn sample_child_health_current_process() {
let _health = crate::child_health::sample_child_health(std::process::id());
}
}
#[cfg(test)]
#[allow(unused_imports)]
mod kiss_cov_gate_refs {
use super::*;
#[test]
fn kiss_cov_unit_names() {
let _ = evaluate_after_acp_silence;
}
}