diff --git a/hive-priv/src/container.rs b/hive-priv/src/container.rs index 7c0bb74e..c25bbdf1 100644 --- a/hive-priv/src/container.rs +++ b/hive-priv/src/container.rs @@ -728,18 +728,31 @@ mod tests { ); } - /// Whether `pid` is gone, or only a zombie awaiting its reaper: either - /// way it is no longer running. + /// Whether `pid` is gone, a zombie (`Z`), or being reaped (`X`): none of + /// these runs again, and a process passes through them in that order, + /// so once this is true it stays true. fn is_dead(pid: &str) -> bool { match std::fs::read_to_string(format!("/proc/{pid}/stat")) { Err(_) => true, - // The state letter follows the parenthesised command name. - Ok(stat) => stat - .rsplit_once(") ") - .is_some_and(|(_, rest)| rest.starts_with('Z')), + Ok(stat) => stat_is_dead(&stat), } } + /// The state letter of a `/proc//stat` line, which follows the + /// parenthesised command name, is `Z` or `X`. + fn stat_is_dead(stat: &str) -> bool { + stat.rsplit_once(") ") + .is_some_and(|(_, rest)| rest.starts_with(['Z', 'X'])) + } + + #[test] + fn zombie_and_reaped_states_count_as_dead() { + assert!(stat_is_dead("42 (sleep) Z 1 42 42")); + assert!(stat_is_dead("42 (sleep) X 1 42 42")); + assert!(!stat_is_dead("42 (sleep) S 1 42 42")); + assert!(!stat_is_dead("42 (sleep) R 1 42 42")); + } + /// A child still running at the limit comes back `TimedOut`, and the kill /// reaches its whole process group: the backgrounded grandchild dies too, /// not just the shell that started it.