use std::sync::Arc;
use beamr::process::ExitReason;
use beamr::scheduler::Scheduler;
use super::{ComponentId, ComponentRecord, ComponentRegistry, RegistryError};
use crate::error::TombstoneKind;
use crate::supervision::observation::{ExitObservation, ObservationError};
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ForcedCleanupReport {
pub children: Vec<ChildCleanup>,
pub supervisor: Option<ProcessCleanup>,
pub monitor_failure: Option<String>,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ChildCleanup {
pub name: String,
pub pid: u64,
pub disposition: ProcessCleanup,
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum ProcessCleanup {
Killed {
tombstone: TombstoneKind,
},
AlreadyDead {
tombstone: TombstoneKind,
},
}
impl ComponentRegistry {
pub(super) fn recover_failed_tree(
&self,
id: ComponentId,
record: &Arc<ComponentRecord>,
) -> Result<Option<ForcedCleanupReport>, RegistryError> {
let monitor_failure = record
.tree
.lock()
.map_err(|_| RegistryError::SynchronizationPoisoned)?
.take()
.and_then(|tree| tree.join_finished(id).err())
.map(|error| error.to_string());
let children = record
.status
.children
.lock()
.map_err(|_| RegistryError::SynchronizationPoisoned)?
.clone();
let mut cleaned_children = Vec::with_capacity(children.len());
for child in children {
cleaned_children.push(ChildCleanup {
name: child.name,
pid: child.pid,
disposition: cleanup_process(id, &self.scheduler, child.pid, self.config)?,
});
}
record
.status
.children
.lock()
.map_err(|_| RegistryError::SynchronizationPoisoned)?
.clear();
let supervisor_pid = *record
.status
.supervisor
.lock()
.map_err(|_| RegistryError::SynchronizationPoisoned)?;
let supervisor = supervisor_pid
.map(|pid| cleanup_process(id, &self.scheduler, pid, self.config))
.transpose()?;
if supervisor.is_some() {
*record
.status
.supervisor
.lock()
.map_err(|_| RegistryError::SynchronizationPoisoned)? = None;
}
if cleaned_children.is_empty() && supervisor.is_none() && monitor_failure.is_none() {
Ok(None)
} else {
Ok(Some(ForcedCleanupReport {
children: cleaned_children,
supervisor,
monitor_failure,
}))
}
}
}
fn cleanup_process(
id: ComponentId,
scheduler: &Arc<Scheduler>,
pid: u64,
config: crate::supervision::LifecycleConfig,
) -> Result<ProcessCleanup, RegistryError> {
let observation = ExitObservation::register(scheduler, pid, config.operation_timeout)
.map_err(|error| recovery_observation_error(id, pid, error))?;
let was_alive = scheduler.process_table().get(pid).is_some();
if was_alive {
scheduler.terminate_process(pid, ExitReason::Kill);
}
let reason = observation
.wait()
.map_err(|error| recovery_observation_error(id, pid, error))?;
let tombstone = tombstone_kind(reason);
if was_alive {
Ok(ProcessCleanup::Killed { tombstone })
} else {
Ok(ProcessCleanup::AlreadyDead { tombstone })
}
}
fn recovery_observation_error(id: ComponentId, pid: u64, error: ObservationError) -> RegistryError {
let detail = match error {
ObservationError::Spawn(detail) => format!("recovery observer spawn failed: {detail}"),
ObservationError::ReadyDeadline => "recovery observer readiness timed out".to_owned(),
ObservationError::Registration(detail) => {
format!("recovery monitor registration failed: {detail}")
}
ObservationError::TombstoneDeadline => {
format!("timed out observing recovered process {pid} tombstone")
}
ObservationError::Protocol(detail) => detail,
ObservationError::CleanupDeadline => "recovery observer cleanup timed out".to_owned(),
};
RegistryError::SupervisorProtocol { id, detail }
}
const fn tombstone_kind(reason: ExitReason) -> TombstoneKind {
match reason {
ExitReason::Normal => TombstoneKind::Normal,
ExitReason::Kill => TombstoneKind::Kill,
ExitReason::Killed => TombstoneKind::Killed,
ExitReason::Error => TombstoneKind::Error,
ExitReason::NoConnection => TombstoneKind::NoConnection,
ExitReason::NoProc => TombstoneKind::NoProcess,
}
}