怎么可以在关闭 exec.Command 打开的进程时保证关闭所有的子进程

263 天前
 dzdh

比如代码这样的

func command(command string, writer io.Writer, notify chan struct{}) {
	r, w := io.Pipe()

	cmd := exec.Command("bash")
	cmd.Dir = "/data"
	cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true} # 这个还不兼容 windows

	cmd.Stdin = r

	stdout, _ := cmd.StdoutPipe()
	stderr, _ := cmd.StderrPipe()
	defer stdout.Close()
	defer stderr.Close()

	cmd.Start()
	go trans(stdout, writer) // iocopy
	go trans(stderr, writer)

	go func() {
		<-notify
		fmt.Println(cmd.Process.Kill())
		w.Close()
	}()

	fmt.Fprint(w, command)
	w.Close()

	cmd.Wait()
}

如果执行 yarn build 会执行一个 nodejs 的构建,但是当 notify 通知后,bash 进程退出了。nodejs 进程僵尸了。

怎么保证所有由 bash 开启的所有子进程都被关闭(哪怕强制退出)

1783 次点击
所在节点    Go 编程语言
16 条回复
ic3z
263 天前
先找到子进程再关闭。
withgeneric
263 天前
你都知道拿 group id 了,你应该知道用 syscall.Kill 啊
dzdh
263 天前
@withgeneric 测试的时候貌似不管用。bash 退了然后 yarn 就直接挂到 init 下了还在跑
withgeneric
263 天前
@dzdh 给 syscall kill 传 group id ,不是 process id
withgeneric
263 天前
@dzdh man7.org/linux/man-pages/man2/kill.2.html

If pid is less than -1, then sig is sent to every process in the
process group whose ID is -pid.

传取反以后的 group id ,读读文档就好
ysc3839
263 天前
之前调查过这个问题,不同操作系统情况不同:
POSIX(Linux 除外)似乎没有通用方法。杀进程组的话,子进程可以创建新的进程组来避免。杀进程树的话,子进程可以 fork 两次来脱离进程树。
Linux 可以用 PID namespace 来实现,当一个 PID namespace 里面的“init”进程退出后,其他进程都会被停止。
Windows 可以用 Job Object 实现。
xhd2015
263 天前
xhd2015
263 天前
我记得这个问题曾经让我很头疼,原因是 go 进程开启了 bash ,bash 执行 git ,git 又启动了子进程,但是最终产生了大量的 defunct 状态的进程项,也就是说进程已经销毁了,但是 pid 还留在注册表上,导致最终系统无法再新建进程。
最后的解决方法与 go 没有关系,这个问题似乎是 git 的问题,我起了一个定时任务,定期检查 fefunct 的进程项,然后通过调用 wait 强行把它们从进程表中删除。
rrfeng
263 天前
为什么要杀子进程?子进程不会正常退出吗?不正常退出要做错误处理吧?硬杀是不是太粗暴了
guo4224
263 天前
僵尸要 wait
dzdh
263 天前
@rrfeng
这是个 deployer 的场景。假设构建需要 10 分钟,设置超时 5 分钟,那确实需要强制 kill


@withgeneric #5
奇怪。今天上班 go run 好了。。之前 nodejs 进程死活杀不掉。。


pgid, _ := syscall.Getpgid(cmd.Process.Pid)
if pgid == -1 {
return
}
syscall.Kill(-pgid, syscall.SIGKILL)
ccsexyz
263 天前
死活杀不掉的看下进程是不是进入 D 状态了
vimiix
263 天前
linux:
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

windows:
cmd.SysProcAttr = &syscall.SysProcAttr{CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP}

这样应该可以?
xuyang2
263 天前
uniquecolesmith
263 天前
使用以下代码解决,来源我的项目 go-zoox/watch: https://github.com/go-zoox/watch/blob/master/process/process.go

```go
if err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL); err != nil {
return fmt.Errorf("failed to kill process: %s", err)
}
```

相关:
- https://github.com/go-zoox/watch/blob/master/process/process.go
- https://medium.com/@felixge/killing-a-child-process-and-all-of-its-children-in-go-54079af94773
xhd2015
262 天前
不要 kill ,要 wait

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/1030102

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX