为什么不能正确杀死儿童过程?

时间:2014-03-18 04:08:28

标签: go exec

当cmd在指定的时间内完成时,以下工作正常。但是,超时不起作用。虽然它确实打印"It's dead Jim",但它不仅无法打印"Done waiting",而且该过程实际上并未被杀死。它继续运行,"Done waiting"永远不会打印。

我认为这是所有相关的代码,但我是Go的新手(这是我尝试过的第一个真正的项目)所以如果这还不够,请告诉我。

func() {
    var output bytes.Buffer
    cmd := exec.Command("Command", args...)
    cmd.Dir = filepath.Dir(srcFile)
    cmd.Stdout, cmd.Stderr = &output, &output
    if err := cmd.Start(); err != nil {
        return err
    }
    defer time.AfterFunc(time.Second*2, func() {
        fmt.Printf("Nobody got time fo that\n")
        if err := cmd.Process.Signal(syscall.SIGKILL); err != nil {
            fmt.Printf("Error:%s\n", err)
        }
        fmt.Printf("It's dead Jim\n")
    }).Stop()
    err := cmd.Wait()
    fmt.Printf("Done waiting\n")
}()

我认为它不应该有所作为,但是值得命令的是go test html。超时的原因是因为我在运行它之前注入了导致无限循环的错误。为了增加混乱,我尝试使用go test net运行它。有一个超时,它工作正常。

5 个答案:

答案 0 :(得分:14)

看起来问题是cmd.Process.Kill()不会终止子进程。看到类似的问题Process.Kill() on child processes

我在这个帖子https://groups.google.com/forum/#!topic/golang-nuts/XoQ3RhFBJl8

中找到了一个解决方案
cmd := exec.Command( some_command )
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
cmd.Start()

pgid, err := syscall.Getpgid(cmd.Process.Pid)
if err == nil {
    syscall.Kill(-pgid, 15)  // note the minus sign
}

cmd.Wait()

作为一个警告,这几乎肯定不会跨平台工作 - 我现在在OSX Yosemite上,而且我愿意打赌它也适用于大多数Linux,但我不够了解关于BSD有意见,我怀疑它可以在Windows上运行。

答案 1 :(得分:3)

仅供参考,我也将我的Windows解决方案放在这里:

func kill(cmd *exec.Cmd) error {
    kill := exec.Command("TASKKILL", "/T", "/F", "/PID", strconv.Itoa(cmd.Process.Pid))
    kill.Stderr = os.Stderr
    kill.Stdout = os.Stdout
    return kill.Run()
 }

答案 2 :(得分:2)

您的调用进程可以使用setsid在posix系统上创建新会话。当您执行以下操作时,您的代码将成为流程组负责人(如果不是这样)。当你杀死进程组负责人时,孩子们也会死亡。至少,这是我的经历。

cmd.SysProcAttr = &syscall.SysProcAttr{Setsid: true}
cmd.Start()
time.Sleep(5)
if err := syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL); err != nil {
        log.Println("failed to kill: ", err)
}

答案 3 :(得分:0)

我不确定何时添加它,但是从Go 1.11开始,您可以将子进程上的Pdeathsig设置为syscall.SIGKILL。父母退出后,这会杀死孩子。

cmd, _ := exec.Command("long-running command")
cmd.SysProcAttr = &syscall.SysProcAttr{
    Pdeathsig: syscall.SIGKILL,
}
cmd.Start()

os.Exit(1)

cmd应该在退出时被杀死。

答案 4 :(得分:-3)

Go的defer语句调度函数调用(延迟函数),在执行defer的函数返回之前立即运行。

推迟后的事情

defer time.AfterFunc(time.Second*2, func() {
    fmt.Printf("Nobody got time fo that\n")
    cmd.Process.Kill()
    fmt.Printf("It's dead Jim\n")
}).Stop()
除非func()结束,否则不会执行

。因此,如果“cmd.Wait()”永远不会结束,则永远不会执行“time.AfterFunc()”。

从延迟中删除“time.AfterFunc(...)”可以解决此问题,因为“time.AfterFunc”可能会等待持续时间过去,然后在其自己的goroutine中调用f

这是一个工作版本。我在我的ubuntu盒子里测试过它的确有效。 将来源保存为wait.go

package main

import "os/exec"
import "time"
import "bytes"
import "fmt"


func main() {
    var output bytes.Buffer
        cmd := exec.Command("sleep", "10s")
        cmd.Stdout, cmd.Stderr = &output, &output
        if err := cmd.Start(); err != nil {
                fmt.Printf("command start error\n")
                return
        }
        time.AfterFunc(time.Second*2, func() {
                fmt.Printf("Nobody got time for that\n")
                cmd.Process.Kill()
                fmt.Printf("It's dead Jim\n")
        })
        cmd.Wait()
        fmt.Printf("Done waiting\n")
}

运行命令:

time go run wait.go

输出:

Nobody got time for that
It's dead Jim
Done waiting

real    0m2.481s
user    0m0.252s
sys 0m0.452s

正如@James Henstridge评论说上述理解不正确。实际上我对延迟的理解不完全。另一半是“延迟函数的参数(如果函数是一个方法,包括接收器),在延迟执行时评估”。因此,在执行延迟时真正创建计时器,因此计时器将超时。

问题在于为什么这个过程无法被杀死。我检查了go的pkg代码,它在* nix中发送了一个SIGKILL系统来杀死进程。无法阻止和忽略SIGKILL。所以它可能是其他可能性,例如过程本身处于TASK_UNINTERRUPTIBLE状态。