我有一个运行超时命令的函数。它看起来像这样:
func run_command(cmdName string, cmdArgs []string, timeout int) (int, string) {
// the command we're going to run
cmd := exec.Command(cmdName, cmdArgs...)
// assign vars for output and stderr
var output bytes.Buffer
var stderr bytes.Buffer
// get the stdout and stderr and assign to pointers
cmd.Stderr = &stderr
cmd.Stdout = &output
// Start the command
if err := cmd.Start(); err != nil {
log.Fatalf("Command not found: %s", cmdName)
}
timer := time.AfterFunc(time.Second*time.Duration(timeout), func() {
err := cmd.Process.Kill()
if err != nil {
panic(err)
}
})
// Here's the good stuff
if err := cmd.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
// Command ! exit 0, capture it
if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
// Check it's nagios compliant
if status.ExitStatus() == 1 || status.ExitStatus() == 2 || status.ExitStatus() == 3 {
return status.ExitStatus(), stderr.String()
} else {
// If not, force an exit code 2
return 2, stderr.String()
}
}
} else {
log.Fatalf("cmd.Wait: %v", err)
}
timer.Stop()
}
// We didn't get captured, continue!
return 0, output.String()
}
现在我希望能够使超时可选。为了稍微捏一下,我试着简单地允许将超时设置为0
,然后在计时器周围加上if语句。它最终看起来像这样。
if timeout > 0 {
timer := time.AfterFunc(time.Second*time.Duration(timeout), func() {
err := cmd.Process.Kill()
if err != nil {
panic(err)
}
})
}
当然,这失败了,因为不再定义计时器timer.Stop()
现在没有定义。
所以我也将timer.Stop()
包装在if语句中。
if timeout > 0 {
timer.Stop()
}
这也没有用。
这样做的正确方法是什么? Golang严格的打字对我来说是新的,所以我很难解决这个问题
答案 0 :(得分:4)
使用context
包可以轻松处理超时。
自Go 1.7以来,golang.org/x/net/context
已成为标准库。
以下是一个例子:
package main
import (
"context"
"os"
"os/exec"
"strconv"
"time"
)
func main() {
timeout, err := strconv.Atoi(os.Args[1])
if err != nil {
panic(err)
}
ctx := context.Background()
if timeout > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(timeout)*time.Second)
defer cancel()
}
cmd := exec.CommandContext(ctx, "sleep", "5")
if err := cmd.Run(); err != nil {
panic(err)
}
}
当超时设置为3秒时,运行sleep 5
:
$ go run main.go 3
panic: signal: killed
goroutine 1 [running]:
panic(0xc7040, 0xc42008c020)
/usr/local/Cellar/go/1.7.4_1/libexec/src/runtime/panic.go:500 +0x1a1
main.main()
/Users/m-morita/work/tmp/20170106/main.go:27 +0x11c
exit status 2
当它设置为10秒或0(=永不超时)时,它会正常结束:
$ go run main.go 10
$ go run main.go 0
答案 1 :(得分:2)
虽然如果没有持续时间,你可以用noop替换timer func,但通常的解决方案是在创建定时器时简单地推迟timer.Stop
调用:
timer := time.AfterFunc(time.Second*time.Duration(timeout), func() {
err := cmd.Process.Kill()
if err != nil {
panic(err)
}
})
defer timer.Stop()
否则,您可以在函数范围内声明timer
并在调用timer.Stop()
之前检查它是否已分配
if timer != nil {
timer.Stop()
}
您还应该注意,exec.Cmd
已经使用Context
超时,通过exec.CommandContext
公开。
答案 2 :(得分:1)
只需在第一个if timeout>之前定义timer变量。 0阻止并使用=而不是:=。
将计时器分配给它var timer *time.Timer
if timeout > 0 {
timer = time.AfterFunc(time.Second*time.Duration(timeout), func() {
err := cmd.Process.Kill()
if err != nil {
panic(err)
}
})
}
检查超时>在timer.Stop()之前仍然需要0,或者,为了减少依赖性,改为timer!= nil。
if timer != nil {
timer.Stop()
}