可选的超时设置在golang中的用法

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

Optional timeouts in golang

问题

我有一个函数,用于在超时时间内运行一个命令。代码如下:

func run_command(cmdName string, cmdArgs []string, timeout int) (int, string) {

  // 要运行的命令
  cmd := exec.Command(cmdName, cmdArgs...)

  // 为输出和错误输出分配变量
  var output bytes.Buffer
  var stderr bytes.Buffer

  // 获取标准输出和错误输出,并分配给指针
  cmd.Stderr = &stderr
  cmd.Stdout = &output

  // 启动命令
  if err := cmd.Start(); err != nil {
    log.Fatalf("Command not found: %s", cmdName)
  }

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

  // 这里是关键
  if err := cmd.Wait(); err != nil {
    if exiterr, ok := err.(*exec.ExitError); ok {
      // 命令退出码不为0,捕获它
      if status, ok := exiterr.Sys().(syscall.WaitStatus); ok {
        // 检查是否符合Nagios标准
        if status.ExitStatus() == 1 || status.ExitStatus() == 2 || status.ExitStatus() == 3 {
          return status.ExitStatus(), stderr.String()
        } else {
          // 如果不符合,强制退出码为2
          return 2, stderr.String()
        }
      }
    } else {
      log.Fatalf("cmd.Wait: %v", err)
    }
  }

  // 没有被捕获,继续执行!
  if timer != nil {
    timer.Stop()
  }
  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不再被定义,timer.Stop()也无法使用。

所以我也将timer.Stop()包裹在if语句中,代码如下:

if timeout > 0 {
  timer.Stop()
}

但这也不起作用。

正确的做法是什么?Golang的严格类型对我来说是新的,所以我对此感到困惑。

英文:

I have a function which runs a command with a timeout. It looks like this:

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

Now I want to be able to make the timeout optional. In order to fudge this a bit, I tried simply allowing timeout to be set to 0 and then having an if statement around the timer. It ended up looking like this.

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

Of course, this failed because timer is no longer defined timer.Stop() isn't defined now.

So I wrapped the timer.Stop() with the if statement as well.

if timeout > 0 {
timer.Stop()
}

This also didn't work.

What is the correct way to do something like this? Golangs strict typing is new to me, so I'm struggling to get my head around it

答案1

得分: 5

使用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
英文:

Using the context package makes it easy to handle timeouts.
golang.org/x/net/context has become a standard library since Go 1.7.
The following is an example:

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

When timeout is set to 3 seconds, and run 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

When it is set to 10 seconds or 0(= never timeout), it ends normally:

$ go run main.go 10 
$ go run main.go 0

答案2

得分: 2

虽然如果没有持续时间,你可以用一个空操作替换计时器函数,但通常的解决方案是在创建计时器时简单地推迟 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 进行访问。

英文:

While you could replace the timer func with a noop if there's no duration, the usual solution is to simply defer the timer.Stop call when you create the timer:

timer := time.AfterFunc(time.Second*time.Duration(timeout), func() {
err := cmd.Process.Kill()
if err != nil {
panic(err)
}
})
defer timer.Stop()

Otherwise, you can declare timer at the function scope and check if it was assigned before calling timer.Stop()

if timer != nil {
timer.Stop()
}

You should also note that an exec.Cmd already makes use of a Context for timeouts, which is exposed via exec.CommandContext.

答案3

得分: 1

在第一个if timeout > 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()之前,仍然需要检查timeout > 0,或者为了减少依赖性,改为timer != nil。

if timer != nil {
  timer.Stop()
}
英文:

Simply define the timer variable before the first if timeout > 0 block and assign the timer to it using = instead of :=.

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

The check for timeout > 0 before timer.Stop() will still be necessary, or, to diminish dependencies, changed to timer != nil.

if timer != nil {
timer.Stop()
}

huangapple
  • 本文由 发表于 2017年1月6日 22:28:54
  • 转载请务必保留本文链接:https://go.coder-hub.com/41507805.html
匿名

发表评论

匿名网友

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

确定