From ea9d5c3ad820cbd3918ac515c2d1fb7822d4a341 Mon Sep 17 00:00:00 2001 From: Linghong Date: Sun, 4 Oct 2026 21:07:49 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Linux=20CI=20=E8=BF=9B?= =?UTF-8?q?=E7=A8=8B=E7=BB=84=E5=83=B5=E5=B0=B8=E6=88=90=E5=91=98=E5=AF=BC?= =?UTF-8?q?=E8=87=B4=20shutdown=20=E8=B6=85=E6=97=B6=20=E8=B0=83=E6=95=B4?= =?UTF-8?q?=20process=5Ftree.rs=20=E7=9A=84=20Unix=20=E8=BF=9B=E7=A8=8B?= =?UTF-8?q?=E7=BB=84=E7=A9=BA=E5=88=A4=EF=BC=9ALinux=20=E4=B8=8B=E6=89=AB?= =?UTF-8?q?=E6=8F=8F=20/proc=20=E5=B9=B6=E5=B0=86=E5=83=B5=E5=B0=B8?= =?UTF-8?q?=E6=88=90=E5=91=98=E8=A7=86=E4=B8=BA=E5=B7=B2=E9=80=80=E5=87=BA?= =?UTF-8?q?=20=E9=81=BF=E5=85=8D=E5=AE=B9=E5=99=A8=20PID=201=20=E4=B8=8D?= =?UTF-8?q?=E5=9B=9E=E6=94=B6=E8=A2=AB=20reparent=20=E7=9A=84=E5=83=B5?= =?UTF-8?q?=E5=B0=B8=E5=AD=99=E8=BF=9B=E7=A8=8B=E6=97=B6=20kill(-pgid,=200?= =?UTF-8?q?)=20=E8=AF=AF=E5=88=A4=E7=BB=84=E4=BB=8D=E5=AD=98=E6=B4=BB?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/codex_app_server/process_tree.rs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/process_tree.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/process_tree.rs index fb8844a95..758e40619 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/process_tree.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/codex_app_server/process_tree.rs @@ -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 { + 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::().ok() != Some(pgid) { + continue; + } + if state != "Z" { + return Some(true); + } + } + Some(false) +} + impl Drop for OwnedProcessTree { fn drop(&mut self) { // Drop 只做应急回收,永远不伪造完成证明;正式终态必须 await shutdown。