golang中的可选超时

时间:2017-01-06 14:28:54

标签: go timer

我有一个运行超时命令的函数。它看起来像这样:

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严格的打字对我来说是新的,所以我很难解决这个问题

3 个答案:

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