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條經驗 獲得超16個贊
考慮使用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 關注
- 153 瀏覽
添加回答
舉報