Corral 能在 30 秒内确保子进程全部被清理

技术宅小李 初级 1小时前 201 浏览 7 点赞 约 3 分钟

在使用 AI 编码代理或 CI 任务时,常会遇到子进程残留、端口占用或 IO 卡死的情况。Corral 通过独立会话和可选 cgroup,保证在命令结束后所有衍生进程都被杀死;若检测不到完整清理则返回 120。下面把原理、使用方法和几处坑点拆开说清楚,照着走基本就能在自己的 runner 里复现。

为什么普通 runner 常会留下进程

  1. 双重 fork 并 setsid

- 进程会在子进程内部再次 fork 并调用 setsid(),形成新的进程组和会话。父进程结束后,信号只会发送到原来的进程组,新的组因为父已经是 init,根本收不到。

  1. 后台进程保持 stdout/stderr 打开

- runner 等待子进程的输出管道关闭。如果后台进程仍持有写端,管道永远不 EOF,导致 runner 卡住。

  1. 进程自行忽略 SIGTERM

- 即使收到终止信号,进程仍继续运行,端口、文件锁等资源被占用,后续任务会因冲突报错。

这三类情况在多数开源 runner(如 GitHub Actions、GitLab CI)里都会出现,因为它们只向直接启动的进程或其进程组发送信号。

Corral 是怎么解决的

  • 独立会话:Corral 为目标命令创建全新的 session(setsid),这样即使命令内部再 fork,也不会把信号回传到父进程。
  • 可选 cgroup:若系统支持 cgroup,Corral 把整个进程树放进一个临时 cgroup,结束时直接删除 cgroup,保证树上所有进程一起被杀。
  • /proc 追踪:在没有 cgroup 的环境下,Corral 通过遍历 /proc 树来追踪子进程。该方式只能在父进程死亡后捕获已改会话的子进程,且无法处理切换到其他用户或交给 systemd 的服务。
  • 退出码 120:在所有清理手段都尝试过后仍发现有进程存活,Corral 会以 120 退出,提示调用者需要人工干预。

基本使用方式

# 让命令在 30 秒内执行完毕,结束后确保没有残留进程
corral --wall 30s -- yourcommand arg1 arg2
  • --wall 30s:设定最大运行时间,超时后 Corral 会强制终止整个进程树。
  • --:后面的所有内容都视为要被管理的命令行,避免参数冲突。
  • 返回码:

- 0:命令正常结束且所有子进程已被清理。
- 124(或类似超时码):命令因超时被杀。
- 120:Corral 未能确认所有进程已死亡。

手动检查残留进程(可选)

如果返回码是 120,可以在同一台机器上运行:

ps -ef | grep yourcommand

定位仍在运行的进程,手动 kill -9 <pid> 或检查 cgroup 状态。

可能的限制与注意点

  • cgroup 权限:创建临时 cgroup 需要 root 或相应的 CAP_SYS_ADMIN 权限。普通 CI 运行器如果没有这些权限,只会回退到 /proc 追踪。
  • 跨用户进程:如果子进程切换到别的用户(例如使用 sudo -u),Corral 的 /proc 追踪将失效,进程会残留。
  • 系统服务交接:某些守护进程会将自身交给 systemd(如 systemd-run),此时即使在同一 session 内,也会在父进程退出后被 systemd 接管,Corral 也无法彻底清除。
  • 资源占用:在高并发 CI 场景下频繁创建 cgroup 可能导致内核的 cgroup 控制块耗尽,需要提前做好配额规划。

小结

Corral 把 “运行命令 → 必须全部清理” 这件事做成了一个可复用的工具:通过独立 session 防止信号泄漏,优先使用 cgroup 实现“一键干掉树”,没有 cgroup 时回退到 /proc 追踪,并在清理失败时用 120 提示。只要你的 CI 环境能提供必要的权限,直接换成 corral --wall … -- <cmd>,大多数后台残留、IO 卡死的问题都能被消灭。

工作流linuxCorralcgroupSIGTERM

全部回复 (1)

想当场把话说完?进全球 AI 聊天室,登录就能开口。

老
老陈 专家 1小时前

timeout 连 setsid 孤儿都杀不死,Corral 报 120 才是硬伤,你这提议根本没用。

0 回复

发表回复

支持 Markdown 格式