use std::{io, time::Duration};
use crate::platform::process::{OwnerDeathCleanup, OwnerDeathCleanupError, OwnerDeathCleanupStage};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ProcessTreeCleanup {
LinuxParentDeathSignal,
WindowsKillOnJobClose,
WindowsAlreadyInJob,
MacosKqueueSupervisorContract,
UnsupportedNoop,
}
pub const MACOS_SUPERVISOR_KILL_DEADLINE: Duration = Duration::from_secs(5);
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct MacosSupervisorContract {
pub watch_pid: MacosSupervisorWatchPid,
pub kqueue_filter: MacosKqueueFilter,
pub kqueue_note: MacosKqueueNote,
pub registration_barrier: MacosSupervisorRegistrationBarrier,
pub race_guard: MacosSupervisorRaceGuard,
pub exit_action: MacosSupervisorExitAction,
pub kill_deadline: Duration,
}
impl MacosSupervisorContract {
pub const fn phase5() -> Self {
Self {
watch_pid: MacosSupervisorWatchPid::BrokerParent,
kqueue_filter: MacosKqueueFilter::Process,
kqueue_note: MacosKqueueNote::Exit,
registration_barrier: MacosSupervisorRegistrationBarrier::BeforeBackendPipePublication,
race_guard: MacosSupervisorRaceGuard::RecheckBrokerAliveAfterRegistration,
exit_action: MacosSupervisorExitAction::SigkillBackend,
kill_deadline: MACOS_SUPERVISOR_KILL_DEADLINE,
}
}
pub const fn kqueue_filter_name(&self) -> &'static str {
match self.kqueue_filter {
MacosKqueueFilter::Process => "EVFILT_PROC",
}
}
pub const fn kqueue_note_name(&self) -> &'static str {
match self.kqueue_note {
MacosKqueueNote::Exit => "NOTE_EXIT",
}
}
pub const fn termination_signal_name(&self) -> &'static str {
match self.exit_action {
MacosSupervisorExitAction::SigkillBackend => "SIGKILL",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MacosSupervisorWatchPid {
BrokerParent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MacosKqueueFilter {
Process,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MacosKqueueNote {
Exit,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MacosSupervisorRegistrationBarrier {
BeforeBackendPipePublication,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MacosSupervisorRaceGuard {
RecheckBrokerAliveAfterRegistration,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MacosSupervisorExitAction {
SigkillBackend,
}
pub const fn macos_supervisor_contract() -> MacosSupervisorContract {
MacosSupervisorContract::phase5()
}
#[derive(Debug, thiserror::Error)]
pub enum ProcessTreeError {
#[error("failed to install Linux parent-death signal: {0}")]
LinuxParentDeathSignal(io::Error),
#[error("failed to create Windows kill-on-close Job Object: {0}")]
WindowsJobCreate(io::Error),
#[error("failed to assign broker process to Windows Job Object: {0}")]
WindowsJobAssign(io::Error),
}
pub fn install_cleanup() -> Result<ProcessTreeCleanup, ProcessTreeError> {
crate::platform::process::install_owner_death_cleanup()
.map(from_facade)
.map_err(from_facade_error)
}
pub fn cleanup_target() -> ProcessTreeCleanup {
from_facade(crate::platform::process::owner_death_cleanup_target())
}
fn from_facade(cleanup: OwnerDeathCleanup) -> ProcessTreeCleanup {
match cleanup {
OwnerDeathCleanup::OwnerDeathSignal => ProcessTreeCleanup::LinuxParentDeathSignal,
OwnerDeathCleanup::KillOnOwnerHandleClose => ProcessTreeCleanup::WindowsKillOnJobClose,
OwnerDeathCleanup::AlreadyContained => ProcessTreeCleanup::WindowsAlreadyInJob,
OwnerDeathCleanup::SupervisorRequired => ProcessTreeCleanup::MacosKqueueSupervisorContract,
OwnerDeathCleanup::Unsupported => ProcessTreeCleanup::UnsupportedNoop,
}
}
fn from_facade_error(error: OwnerDeathCleanupError) -> ProcessTreeError {
match error.stage {
OwnerDeathCleanupStage::RequestSignal => {
ProcessTreeError::LinuxParentDeathSignal(error.source)
}
OwnerDeathCleanupStage::CreateContainer => ProcessTreeError::WindowsJobCreate(error.source),
OwnerDeathCleanupStage::JoinContainer => ProcessTreeError::WindowsJobAssign(error.source),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn cleanup_target_model_states_phase_5_platform_contracts() {
for (reported, expected) in [
(
OwnerDeathCleanup::OwnerDeathSignal,
ProcessTreeCleanup::LinuxParentDeathSignal,
),
(
OwnerDeathCleanup::KillOnOwnerHandleClose,
ProcessTreeCleanup::WindowsKillOnJobClose,
),
(
OwnerDeathCleanup::AlreadyContained,
ProcessTreeCleanup::WindowsAlreadyInJob,
),
(
OwnerDeathCleanup::SupervisorRequired,
ProcessTreeCleanup::MacosKqueueSupervisorContract,
),
(
OwnerDeathCleanup::Unsupported,
ProcessTreeCleanup::UnsupportedNoop,
),
] {
assert_eq!(from_facade(reported), expected, "{reported:?}");
}
}
#[test]
fn a_failure_keeps_the_step_it_failed_at() {
use crate::platform::process::OwnerDeathCleanupStage;
let staged = |stage| OwnerDeathCleanupError {
stage,
source: io::Error::from_raw_os_error(5),
};
assert!(matches!(
from_facade_error(staged(OwnerDeathCleanupStage::RequestSignal)),
ProcessTreeError::LinuxParentDeathSignal(_)
));
assert!(matches!(
from_facade_error(staged(OwnerDeathCleanupStage::CreateContainer)),
ProcessTreeError::WindowsJobCreate(_)
));
assert!(matches!(
from_facade_error(staged(OwnerDeathCleanupStage::JoinContainer)),
ProcessTreeError::WindowsJobAssign(_)
));
}
#[test]
fn cleanup_target_is_explicit_for_current_platform() {
let target = cleanup_target();
assert_ne!(
target,
ProcessTreeCleanup::UnsupportedNoop,
"every shipped host names a contract; UnsupportedNoop means one was not taught"
);
assert_eq!(
target,
install_cleanup().expect("installing must succeed where a target is claimed"),
"the target must be what installing actually reports"
);
}
#[test]
fn macos_supervisor_contract_pins_phase_5_cleanup_requirements() {
let contract = macos_supervisor_contract();
assert_eq!(contract.watch_pid, MacosSupervisorWatchPid::BrokerParent);
assert_eq!(contract.kqueue_filter_name(), "EVFILT_PROC");
assert_eq!(contract.kqueue_note_name(), "NOTE_EXIT");
assert_eq!(
contract.registration_barrier,
MacosSupervisorRegistrationBarrier::BeforeBackendPipePublication
);
assert_eq!(
contract.race_guard,
MacosSupervisorRaceGuard::RecheckBrokerAliveAfterRegistration
);
assert_eq!(contract.termination_signal_name(), "SIGKILL");
assert_eq!(contract.kill_deadline, Duration::from_secs(5));
}
}