You've already forked agentic-coding-workflow
55 lines
1.5 KiB
Go
55 lines
1.5 KiB
Go
//go:build !windows
|
|
|
|
package proc
|
|
|
|
import (
|
|
"errors"
|
|
"os/exec"
|
|
"syscall"
|
|
"time"
|
|
)
|
|
|
|
// unixGroup 用 POSIX 进程组实现整树终止:子进程经 Setpgid 成为新进程组组长
|
|
// (pgid == pid),向负 pgid 发信号即覆盖整个进程组的所有子孙。
|
|
type unixGroup struct {
|
|
pgid int
|
|
}
|
|
|
|
func newGroup() Group { return &unixGroup{} }
|
|
|
|
func (g *unixGroup) Prepare(cmd *exec.Cmd) {
|
|
if cmd.SysProcAttr == nil {
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{}
|
|
}
|
|
cmd.SysProcAttr.Setpgid = true
|
|
}
|
|
|
|
func (g *unixGroup) Adopt(cmd *exec.Cmd) error {
|
|
if cmd.Process == nil {
|
|
return errors.New("proc: Adopt before Start")
|
|
}
|
|
// Setpgid 使子进程成为新进程组组长,pgid == pid。
|
|
g.pgid = cmd.Process.Pid
|
|
return nil
|
|
}
|
|
|
|
func (g *unixGroup) TerminateGroup(done <-chan struct{}, grace time.Duration) {
|
|
if g.pgid <= 0 {
|
|
return
|
|
}
|
|
// 向负 pgid 发信号 = 向整个进程组(含所有子孙)发信号。忽略 ESRCH 等错误。
|
|
_ = syscall.Kill(-g.pgid, syscall.SIGTERM)
|
|
select {
|
|
case <-done:
|
|
// 组长已被回收(Cmd.Wait 返回),但其子孙(如 trap SIGTERM 的
|
|
// vite/esbuild/node)可能仍存活于同一进程组。无条件向整组补发一次
|
|
// SIGKILL 以确保不留下任何孤儿;若组已全灭,内核返回 ESRCH,无害。
|
|
_ = syscall.Kill(-g.pgid, syscall.SIGKILL)
|
|
case <-time.After(grace):
|
|
// 组长自身超过 grace 仍未退出:直接对整组 SIGKILL。
|
|
_ = syscall.Kill(-g.pgid, syscall.SIGKILL)
|
|
}
|
|
}
|
|
|
|
func (g *unixGroup) Close() {}
|