我正在尝试从命令的 Stdout 中读取,但每隔(大约)50 次它就会冻结。
func runProcess(process *exec.Cmd) (string, string, error) {
var stdout strings.Builder
var stderr string
process := exec.Command(programPath, params...)
go func() {
pipe, err := process.StderrPipe()
if err != nil {
return
}
buf, err := io.ReadAll(pipe)
if err != nil {
log.Warn("Error reading stderr: %v", err)
}
stderr = string(buf)
}()
pipe, err := process.StdoutPipe()
if err = process.Start(); err != nil {
return "", "", err
}
buf := make([]byte, 1024)
read, err := pipe.Read(buf) // it reads correctly from the pipe
for err == nil && read > 0 {
_, err = stdout.Write(buf[:read])
read, err = pipe.Read(buf) // this is where is stalls
}
if err = process.Wait(); err != nil {
return stdout.String(), stderr, err
}
return stdout.String(), stderr, nil
}
Run Code Online (Sandbox Code Playgroud)
我尝试使用stdout, err := io.ReadAll(pipe)一次读取所有内容而不是读取块,但我得到了相同的行为。调用的程序似乎执行成功。它的日志文件已创建并已完成。另外,当我第一次从管道中读取时(在循环之前),所有的输出都在那里。但是在循环内部,当.Read()第二次调用 并且它应该返回一个 EOF(输出小于 1024 字节)时,它会冻结。
这段代码中有许多竞争条件。一般来说,如果你创建一个 goroutine,应该有某种同步——比如 a chan, sync.Mutex, sync.WaitGroup, 或 atomic。
修复比赛条件。
打电话StderrPipe()之前先打电话Start()。代码不这样做。
在返回之前等待 goroutine 完成。
竞争条件可能会破坏exec.Cmd结构......这可能意味着它泄漏了一个管道,这可以解释为什么Read()挂起(因为管道的写端没有关闭)。
根据经验,始终修复竞争条件。将它们视为高优先级错误。
这是一个草图,说明如何在没有竞争条件的情况下编写它:
func runProcess(process *exec.Cmd) (stdout, stderr string, err error) {
outPipe, err := process.StdoutPipe()
if err != nil {
return "", "", err
}
// Call StderrPipe BEFORE Start().
// Easy way to do it: outside the goroutine.
errPipe, err := process.StderrPipe()
if err != nil {
return "", "", err
}
// Start process.
if err := process.Start(); err != nil {
return "", "", err
}
// Read stderr in goroutine.
var wg sync.WaitGroup
var stderrErr error
wg.Add(1)
go func() {
defer wg.Done()
data, err := ioutil.ReadAll(errPipe)
if err != nil {
stderrErr = err
} else {
stderr = string(data)
}
}()
// Read stdout in main thread.
data, stdoutErr := ioutil.ReadAll(outPipe)
// Wait until we are done reading stderr.
wg.Wait()
// Wait for process to finish.
if err := process.Wait(); err != nil {
return "", "", err
}
// Handle error from reading stdout.
if stdoutErr != nil {
return "", "", stderrErr
}
// Handle error from reading stderr.
if stderrErr != nil {
return "", "", stderrErr
}
stdout = string(data)
return stdout, stderr, nil
}
Run Code Online (Sandbox Code Playgroud)
所有这些都是由os/exec包自动完成的。您可以使用任何io.WriterforStdout和Stderr,您不仅限于*os.File。
func runProcess(process *exec.Cmd) (stdout, stderr string, err error) {
var stdoutbuf, stderrbuf bytes.Buffer
process.Stdout = &stdoutbuf
process.Stderr = &stderrbuf
if err := process.Run(); err != nil {
return "", "", err
}
return stdoutbuf.String(), stderrbuf.String(), nil
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
101 次 |
| 最近记录: |