修复 Linux CI 进程组僵尸成员导致 shutdown 超时
Project CI / AI game creator shell Rust crates (pull_request) Successful in 6m41s
Project CI / AI game creator shell Rust lane 2/2 (pull_request) Successful in 8m12s
Project CI / AI game creator shell Rust lane 1/2 (pull_request) Successful in 8m25s
Project CI / Backend tests (pull_request) Successful in 9m8s
Project CI / Frontend tests (pull_request) Successful in 3m41s
Project CI / AI game creator shell web tests (pull_request) Successful in 3m44s
Project CI / Repository checks (pull_request) Successful in 5m51s
Project CI / Native shell tests (pull_request) Successful in 7m38s

调整 process_tree.rs 的 Unix 进程组空判:Linux 下扫描 /proc 并将僵尸成员视为已退出
避免容器 PID 1 不回收被 reparent 的僵尸孙进程时 kill(-pgid, 0) 误判组仍存活
This commit is contained in:
2026-10-04 21:07:49 +08:00
parent 3db120d72c
commit ea9d5c3ad8
@@ -135,6 +135,10 @@ impl OwnedProcessTree {
.ok()
.filter(|pid| *pid > 0)
.ok_or("app-server-process-owner-missing")?;
#[cfg(target_os = "linux")]
if let Some(has_live_member) = linux_process_group_has_live_member(pid) {
return Ok(!has_live_member);
}
if unsafe { libc::kill(-pid, 0) } == 0 {
return Ok(false);
}
@@ -183,6 +187,34 @@ impl OwnedProcessTree {
}
}
/// 容器 PID 1 不回收被 reparent 的僵尸孙进程时,kill(-pgid, 0) 仍认为组存活;
/// 僵尸对退役语义等价于已退出,扫描 /proc 时只把非僵尸成员视为存活。
#[cfg(all(unix, target_os = "linux"))]
fn linux_process_group_has_live_member(pgid: i32) -> Option<bool> {
for entry in std::fs::read_dir("/proc").ok()? {
let entry = entry.ok()?;
let Ok(stat) = std::fs::read_to_string(entry.path().join("stat")) else {
continue;
};
let Some(close) = stat.rfind(')') else {
continue;
};
let mut fields = stat[close + 1..].split_whitespace();
let (Some(state), Some(_ppid), Some(member_pgid)) =
(fields.next(), fields.next(), fields.next())
else {
continue;
};
if member_pgid.parse::<i32>().ok() != Some(pgid) {
continue;
}
if state != "Z" {
return Some(true);
}
}
Some(false)
}
impl Drop for OwnedProcessTree {
fn drop(&mut self) {
// Drop 只做应急回收,永远不伪造完成证明;正式终态必须 await shutdown。