子进程组管理(INV-EXEC-PROC-01)

wesgine 通过 proc.Command 统一管理所有子进程的创建和销毁,确保取消时整个进程树被干净杀掉。


问题背景

exec.CommandContext 只向直接子进程发 SIGKILL:

wesgine → sh -c "npm ls -g"
              ↓
           sh(被杀)
              ↓
           node(活着,持有 stdout 管道写端)
              ↓
           cmd.Wait() 永不返回

取消返回了,工作没有停。


解决方案:proc.Command

internal/infra/proc 是唯一的子进程创建原语:

cmd := proc.Command(ctx, "sh", "-c", "npm ls -g")

它做三件事:

  1. Setpgid = true:创建新进程组
  2. Cancel:ctx 取消时 SIGKILL 整个进程组
  3. WaitDelay:给 Wait() 一个有限时间上界

禁止事项

禁止原因
exec.Command(...)没有进程组管理
exec.CommandContext(ctx, ...)只杀直接子进程
exec.Cmd{} 复合字面量最不像 fork 的拼写
修改 cmd.Cancel会关掉进程组杀死功能
修改 cmd.WaitDelayWaitDelay=0 恢复原缺陷

豁免文件

只有 3 个文件被允许不使用 proc.Command,但各有义务:

文件义务
tool/host_shell.go必须自设 Cancel + WaitDelay
adapter/stt/tool.go脱离 ctx(转写活过请求),必须自设 SysProcAttr 建组
第三个豁免位按需评估

闸门验证

scripts/audit-lock/39-proc-single-spawn.sh:


进程超时后清理

前台 exec(INV-EXEC-11)

ctx 结束后必须调用 KillProcessGroup() 清理整个进程组。

管道排空(INV-EXEC-12)

进程退出后 stdout 排空最多等 5s:

管道消费(INV-EXEC-DRAIN-01)

输出读取必须无条件消费管道直到 EOF:


并发验证

规则的正确性不能只靠 grep:

go test ./internal/infra/proc/ 是不变量的实质验证。


相关文档