Why won't Go kill a child process correctly?

后端 未结 5 543
小蘑菇
小蘑菇 2020-12-14 23:43

The following works just fine when cmd finishes in the allotted time. However, the timeout is not working. While it does print \"It\'s dead Jim\", not only do

相关标签:
5条回答
  • Looks like the problem is that cmd.Process.Kill() doesn't kill child processes. See this similar question Process.Kill() on child processes

    I found a solution in this thread 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()
    

    As a caveat this will almost certainly not work across platforms - I'm on OSX Yosemite at the moment, and I'd be willing to bet it'd work on most Linuxes as well, but I don't know enough about BSD to have an opinion and I doubt it would work on Windows.

    0 讨论(0)
  • 2020-12-15 00:16

    Just for reference, I'll put my Windows solution here as well:

    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()
     }
    
    0 讨论(0)
  • 2020-12-15 00:21

    I'm not sure when it was added, but as of Go 1.11 you can set the Pdeathsig on a subprocess to syscall.SIGKILL. This will kill the child when the parent exits.

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

    The cmd should be killed on exit.

    0 讨论(0)
  • 2020-12-15 00:22

    Your calling process can create a new session on posix systems with setsid. When you execute the following your code becomes the process group leader if (it isn't already that is). When you kill the process group leader the children die too. At least, that is my experience.

    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)
    }
    
    0 讨论(0)
  • 2020-12-15 00:35

    Go's defer statement schedules a function call (the deferred function) to be run immediately before the function executing the defer returns.

    So the things after 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()
    

    wouldn't be executed unless func() ends. Therefore, if "cmd.Wait()" never end, the "time.AfterFunc()" is never executed.

    Removing "time.AfterFunc(...)" from defer can fix this problem, since "time.AfterFunc" could waits for the duration to elapse and then calls f in its own goroutine.

    Here is a working version. I tested in my ubuntu box and it works. Save source as 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")
    }
    

    Run the command:

    time go run wait.go
    

    Output:

    Nobody got time for that
    It's dead Jim
    Done waiting
    
    real    0m2.481s
    user    0m0.252s
    sys 0m0.452s
    

    As @James Henstridge has commented that the above understanding is incorrect. Actually I had incomplete understanding of defer. The other half is "The arguments to the deferred function (which include the receiver if the function is a method) are evaluated when the defer executes". So the timer is truly created when defer is executed and thus timer will time out.

    The problem is really why the process cannot be killed. I checked the go's pkg's code, it sends a SIGKILL in *nix like system to kill the process. The SIGKILL cannot be blocked and ignored. So it could be other possibilites such as the process itself is in TASK_UNINTERRUPTIBLE state.

    0 讨论(0)
提交回复
热议问题