golang 中的可选超时

jax*_*orm 2 timer go

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

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()
}
Run Code Online (Sandbox Code Playgroud)

现在我希望能够使超时成为可选的。为了稍微弥补这一点,我尝试简单地允许将超时设置为0,然后在计时器周围添加一个 if 语句。它最终看起来像这样。

if timeout > 0 {
  timer := time.AfterFunc(time.Second*time.Duration(timeout), func() {
    err := cmd.Process.Kill()
    if err != nil {
      panic(err)
    }
  })
}
Run Code Online (Sandbox Code Playgroud)

当然,这失败了,因为计时器不再定义timer.Stop()了。

所以我timer.Stop()也用 if 语句包装了它。

if timeout > 0 {
  timer.Stop()
}
Run Code Online (Sandbox Code Playgroud)

这也行不通。

做这样的事情的正确方法是什么?Golang 严格类型对我来说是新的,所以我很难理解它

min*_*oyo 7

使用该context包可以轻松处理超时。 golang.org/x/net/context自 Go 1.7 起已成为标准库。下面是一个例子:

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)
    }
}
Run Code Online (Sandbox Code Playgroud)

当超时设置为 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
Run Code Online (Sandbox Code Playgroud)

当设置为10秒或0(=永不超时)时,正常结束:

$ go run main.go 10 
$ go run main.go 0
Run Code Online (Sandbox Code Playgroud)