2 回答
TA贡献1826条经验 获得超6个赞
当你的函数退出时,你正在关闭通道ExecCommand。由于您在 Goroutine 中发送消息,因此无法保证该消息会在函数退出之前发送。事实上,我每次跑步都是在之后发生的。如果没有第一个可推迟的内容,您的测试将正常工作。
defer func() {
log.Printf("waitDone addr:%v\n", &waitDone)
log.Printf("close waitdone channel\n")
close(waitDone) // <- here
}()
go func() {
err = cmd.Run()
log.Printf("waitDone addr:%v\n", &waitDone)
waitDone <- struct{}{} // <- and here
}()
由于您已经在使用超时上下文,因此这将非常适合
cmd = exec.CommandContext(ctx, "bash", "-c", "--", command)
// cmd = exec.Command("bash", "-c", "--", command)
这可以使您免于使用这种复杂的逻辑来检查超时。
TA贡献1801条经验 获得超15个赞
考虑使用exec.CommandContext而不是自己编写此代码。
在命令完成之前上下文超时的情况下,该ExecCommand
函数可以在 Run goroutine 发送到通道之前关闭通道。这会引起恐慌。
waitDone
由于应用程序在执行后不会收到任何消息close(waitDone)
,因此关闭通道是没有意义的。
如果删除关闭通道的代码,就会暴露另一个问题。因为是一个无缓冲的通道,所以在超时情况下,waitDone
Run goroutine 将在发送时永远阻塞。waitDone
调用cmd.Run()
启动一个 goroutine 将数据复制到stdout
和stderr
。无法保证这些 goroutine 在ExecCommand
调用convertStr(stdout)
或之前执行完毕convertStr(stderr)
。
这是解决所有这些问题的一个方法:
func ExecCommand(command string, timeout time.Duration) (string, error) {
log.Printf("command:%v, timeout:%v", command, timeout)
ctx, cancelFn := context.WithTimeout(context.Background(), timeout)
defer cancelFn()
var stdout, stderr bytes.Buffer
cmd := exec.Command("bash", "-c", "--", command)
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
err := cmd.Start()
if err != nil {
return "", err
}
go func() {
<-ctx.Done()
if ctx.Err() == context.DeadlineExceeded {
log.Printf("timeout to kill process, %v", cmd.Process.Pid)
syscall.Kill(-cmd.Process.Pid, syscall.SIGKILL)
}
}()
err = cmd.Wait()
var result string
if err != nil {
result = stderr.String()
} else {
result = stdout.String()
}
}
- 2 回答
- 0 关注
- 110 浏览
添加回答
举报