当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
运行它。有一个超时,它工作正常。
答案 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状态。