为什么Go无法正确地终止子进程?

huangapple go评论91阅读模式
英文:

Why won't Go kill a child process correctly?

问题

以下是翻译好的内容:

当cmd在分配的时间内完成时,以下代码可以正常工作。然而,超时并没有起作用。虽然它打印了"It's dead Jim",但它不仅没有打印"Done waiting",而且进程实际上没有被终止。它继续运行,而"Done waiting"从未打印出来。

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运行它。有一个超时,并且它正常工作。

英文:

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 does it fail to print "Done waiting", but the process is not actually killed. It continues to run, and "Done waiting" never prints.

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")
}()

I don't think it should make a difference, but for what it's worth the command is go test html. The reason it's timing out is because I'm injecting an error that causes an infinite loop before running it. To add to the confusion, I tried running it with go test net. There was a timeout, and it worked correctly.

答案1

得分: 23

看起来问题是cmd.Process.Kill()不能杀死子进程。可以参考这个类似的问题:https://stackoverflow.com/questions/24982845/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)  // 注意负号
}

cmd.Wait()

需要注意的是,这个解决方案几乎肯定不能跨平台使用。我目前在OSX Yosemite上,我敢打赌它在大多数Linux上也能工作,但我对BSD了解不够,而且我怀疑它在Windows上也不会工作。

英文:

Looks like the problem is that cmd.Process.Kill() doesn't kill child processes. See this similar question https://stackoverflow.com/questions/24982845/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.

答案2

得分: 13

我不确定是在什么时候添加的,但是从 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 应该被杀死。

英文:

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.

答案3

得分: 11

只是为了参考,我也会在这里提供我的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()
}

希望对你有帮助!

英文:

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()
 }

答案4

得分: 5

你的调用过程可以使用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)
}
英文:

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)
}

答案5

得分: -3

Go的defer语句会安排一个函数调用(被延迟的函数)在执行defer的函数返回之前立即运行。

因此,在defer之后的代码块中,除非func()函数执行结束,否则这些代码不会被执行。因此,如果"cmd.Wait()"永远不会结束,那么"time.AfterFunc()"也永远不会被执行。

从defer中移除"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所评论的,上述理解是不正确的。实际上,我对defer的理解是不完整的。另一半是"被延迟的函数的参数(如果函数是一个方法,则包括接收器)在defer执行时被求值"。因此,计时器在defer执行时真正创建,并且计时器会超时。

问题实际上是为什么进程无法被杀死。我检查了Go的包的代码,它在类Unix系统中发送一个SIGKILL信号来杀死进程。SIGKILL信号无法被阻塞和忽略。因此,可能是其他原因,比如进程本身处于TASK_UNINTERRUPTIBLE状态。

英文:

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.

huangapple
  • 本文由 发表于 2014年3月18日 12:08:27
  • 转载请务必保留本文链接:https://go.coder-hub.com/22470193.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定