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

2024-04-07 00:35:19 +08:00
 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 开启的所有子进程都被关闭(哪怕强制退出)

2147 次点击
所在节点    Go 编程语言
16 条回复
ic3z
2024-04-07 00:46:52 +08:00
先找到子进程再关闭。
withgeneric
2024-04-07 01:51:43 +08:00
你都知道拿 group id 了,你应该知道用 syscall.Kill 啊
dzdh
2024-04-07 03:30:44 +08:00
@withgeneric 测试的时候貌似不管用。bash 退了然后 yarn 就直接挂到 init 下了还在跑
withgeneric
2024-04-07 03:32:08 +08:00
@dzdh 给 syscall kill 传 group id ,不是 process id
withgeneric
2024-04-07 03:34:01 +08:00
@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
2024-04-07 03:58:07 +08:00
之前调查过这个问题,不同操作系统情况不同:
POSIX(Linux 除外)似乎没有通用方法。杀进程组的话,子进程可以创建新的进程组来避免。杀进程树的话,子进程可以 fork 两次来脱离进程树。
Linux 可以用 PID namespace 来实现,当一个 PID namespace 里面的“init”进程退出后,其他进程都会被停止。
Windows 可以用 Job Object 实现。
xhd2015
2024-04-07 07:41:52 +08:00
xhd2015
2024-04-07 07:45:31 +08:00
我记得这个问题曾经让我很头疼,原因是 go 进程开启了 bash ,bash 执行 git ,git 又启动了子进程,但是最终产生了大量的 defunct 状态的进程项,也就是说进程已经销毁了,但是 pid 还留在注册表上,导致最终系统无法再新建进程。
最后的解决方法与 go 没有关系,这个问题似乎是 git 的问题,我起了一个定时任务,定期检查 fefunct 的进程项,然后通过调用 wait 强行把它们从进程表中删除。
rrfeng
2024-04-07 08:55:07 +08:00
为什么要杀子进程?子进程不会正常退出吗?不正常退出要做错误处理吧?硬杀是不是太粗暴了
guo4224
2024-04-07 09:08:33 +08:00
僵尸要 wait
dzdh
2024-04-07 13:08:08 +08:00
@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
2024-04-07 14:45:07 +08:00
死活杀不掉的看下进程是不是进入 D 状态了
vimiix
2024-04-07 16:17:46 +08:00
linux:
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

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

这样应该可以?
xuyang2
2024-04-07 16:23:59 +08:00
uniquecolesmith
2024-04-07 16:39:29 +08:00
使用以下代码解决,来源我的项目 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
2024-04-07 22:19:07 +08:00
不要 kill ,要 wait

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

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

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

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

© 2021 V2EX