V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
The Go Programming Language
http://golang.org/
Go Playground
Go Projects
Revel Web Framework
dzdh
V2EX  ›  Go 编程语言

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

  •  
  •   dzdh · 248 天前 · 1769 次点击
    这是一个创建于 248 天前的主题,其中的信息可能已经有所发展或是发生改变。

    比如代码这样的

    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 开启的所有子进程都被关闭(哪怕强制退出)

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

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

    这样应该可以?
    uniquecolesmith
        15
    uniquecolesmith  
       248 天前
    使用以下代码解决,来源我的项目 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
        16
    xhd2015  
       247 天前 via iPhone
    不要 kill ,要 wait
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   3944 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 21ms · UTC 10:10 · PVG 18:10 · LAX 02:10 · JFK 05:10
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.