use std::collections::{HashMap, HashSet};
use crate::platform::process::DescendantEvent;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct Descendant {
pub parent: u32,
pub created: u64,
}
pub(crate) fn descendants(
root: u32,
root_created: u64,
table: &[(u32, u32)],
created_of: &mut dyn FnMut(u32) -> Option<u64>,
) -> HashMap<u32, Descendant> {
let mut children: HashMap<u32, Vec<u32>> = HashMap::new();
for &(pid, parent) in table {
if pid != 0 && pid != parent {
children.entry(parent).or_default().push(pid);
}
}
let mut found: HashMap<u32, Descendant> = HashMap::new();
let mut visited: HashSet<u32> = HashSet::from([root]);
let mut stack = vec![(root, root_created)];
while let Some((parent, parent_created)) = stack.pop() {
for &child in children.get(&parent).map_or(&[][..], Vec::as_slice) {
if !visited.insert(child) {
continue;
}
let Some(created) = created_of(child) else {
continue;
};
if created < parent_created {
continue;
}
found.insert(child, Descendant { parent, created });
stack.push((child, created));
}
}
found
}
pub(crate) fn diff(
previous: &HashMap<u32, Descendant>,
current: &HashMap<u32, Descendant>,
) -> Vec<DescendantEvent> {
let mut events = Vec::new();
for (&pid, old) in previous {
match current.get(&pid) {
Some(new) if new.created == old.created => {}
_ => events.push(DescendantEvent::Exited(pid)),
}
}
for (&pid, new) in current {
match previous.get(&pid) {
Some(old) if old.created == new.created => {}
_ => events.push(DescendantEvent::Started {
pid,
parent_pid: Some(new.parent),
}),
}
}
events
}
pub(crate) fn pump(
stopped: impl Fn() -> bool,
mut poll: impl FnMut() -> Option<HashMap<u32, Descendant>>,
emit: &dyn Fn(DescendantEvent),
mut wait: impl FnMut() -> bool,
) {
let mut known: HashMap<u32, Descendant> = HashMap::new();
loop {
if stopped() {
emit(DescendantEvent::Completed);
return;
}
let Some(current) = poll() else {
for pid in known.into_keys() {
emit(DescendantEvent::Exited(pid));
}
emit(DescendantEvent::Completed);
return;
};
for event in diff(&known, ¤t) {
emit(event);
}
known = current;
if wait() {
emit(DescendantEvent::Completed);
return;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::RefCell;
fn stamps(pairs: &[(u32, u64)]) -> impl FnMut(u32) -> Option<u64> + '_ {
move |pid| pairs.iter().find(|(p, _)| *p == pid).map(|(_, t)| *t)
}
#[test]
fn the_walk_finds_children_and_grandchildren_with_their_immediate_parent() {
let table = [(10, 1), (20, 10), (30, 20), (40, 10), (50, 999)];
let times = [(20, 200), (30, 300), (40, 250), (50, 500)];
let got = descendants(10, 100, &table, &mut stamps(×));
assert_eq!(got.len(), 3);
assert_eq!(
got[&20],
Descendant {
parent: 10,
created: 200
}
);
assert_eq!(
got[&30],
Descendant {
parent: 20,
created: 300
}
);
assert_eq!(
got[&40],
Descendant {
parent: 10,
created: 250
}
);
assert!(!got.contains_key(&50), "an unrelated tree is not ours");
}
#[test]
fn a_child_older_than_its_parent_is_a_reused_parent_id_and_is_rejected() {
let table = [(20, 10), (30, 10)];
let times = [(20, 50), (30, 150)];
let got = descendants(10, 100, &table, &mut stamps(×));
assert_eq!(got.keys().copied().collect::<Vec<_>>(), vec![30]);
}
#[test]
fn an_unreadable_creation_time_is_skipped_not_guessed() {
let table = [(20, 10), (30, 10)];
let times = [(30, 150)];
let got = descendants(10, 100, &table, &mut stamps(×));
assert_eq!(got.keys().copied().collect::<Vec<_>>(), vec![30]);
}
#[test]
fn creation_times_are_only_looked_up_for_processes_under_the_root() {
let table = [(20, 10), (60, 99), (70, 99), (80, 99)];
let asked = RefCell::new(Vec::new());
let mut created = |pid| {
asked.borrow_mut().push(pid);
Some(500)
};
let _ = descendants(10, 100, &table, &mut created);
assert_eq!(*asked.borrow(), vec![20]);
}
#[test]
fn self_parented_and_cyclic_rows_terminate_and_never_report_the_root() {
let table = [(0, 0), (10, 20), (20, 10), (30, 30)];
let times = [(20, 200), (10, 100), (30, 300)];
let got = descendants(10, 100, &table, &mut stamps(×));
assert_eq!(got.keys().copied().collect::<Vec<_>>(), vec![20]);
}
#[test]
fn diff_reports_starts_and_exits_and_stays_silent_when_nothing_changed() {
let d = |parent, created| Descendant { parent, created };
let a: HashMap<_, _> = [(20, d(10, 200)), (30, d(20, 300))].into();
let b: HashMap<_, _> = [(20, d(10, 200)), (40, d(10, 400))].into();
assert!(diff(&a, &a).is_empty());
let mut events = diff(&a, &b);
events.sort_by_key(|event| format!("{event:?}"));
assert_eq!(
events,
vec![
DescendantEvent::Exited(30),
DescendantEvent::Started {
pid: 40,
parent_pid: Some(10)
},
]
);
}
#[test]
fn a_reused_pid_is_an_exit_then_a_start_never_silence() {
let d = |parent, created| Descendant { parent, created };
let before: HashMap<_, _> = [(20, d(10, 200))].into();
let after: HashMap<_, _> = [(20, d(10, 900))].into();
let events = diff(&before, &after);
assert_eq!(events.len(), 2);
assert!(events.contains(&DescendantEvent::Exited(20)));
assert!(events.contains(&DescendantEvent::Started {
pid: 20,
parent_pid: Some(10)
}));
}
#[test]
fn the_pump_emits_each_change_once_then_exits_everything_when_the_root_goes() {
let d = |parent, created| Descendant { parent, created };
let mut polls = vec![
Some(HashMap::from([(20, d(10, 200))])),
Some(HashMap::from([(20, d(10, 200)), (30, d(20, 300))])),
None,
]
.into_iter();
let seen = RefCell::new(Vec::new());
pump(
|| false,
|| polls.next().flatten(),
&|event| seen.borrow_mut().push(event),
|| false,
);
let seen = seen.into_inner();
assert_eq!(
seen.first(),
Some(&DescendantEvent::Started {
pid: 20,
parent_pid: Some(10)
})
);
assert!(seen.contains(&DescendantEvent::Started {
pid: 30,
parent_pid: Some(20)
}));
assert!(seen.contains(&DescendantEvent::Exited(20)));
assert!(seen.contains(&DescendantEvent::Exited(30)));
assert_eq!(seen.last(), Some(&DescendantEvent::Completed));
assert_eq!(
seen.iter()
.filter(|e| **e == DescendantEvent::Completed)
.count(),
1
);
}
#[test]
fn a_cancelled_pump_completes_without_polling() {
let seen = RefCell::new(Vec::new());
pump(
|| true,
|| panic!("a stopped pump must not poll"),
&|event| seen.borrow_mut().push(event),
|| panic!("a stopped pump must not wait"),
);
assert_eq!(seen.into_inner(), vec![DescendantEvent::Completed]);
}
}