Skip to content

Commit 2275dcf

Browse files
authored
Start/Wait on exec.Cmd when using pipes (#611)
This might've been the cause for the flaky test we saw, because the docs say that `cmd.Run()` in combination with `StdoutPipe`/`StderrPipe` is wrong: > `Wait` will close the pipe after seeing the command exit, so most callers > need not close the pipe themselves. It is thus incorrect to call `Wait` > before all reads from the pipe have completed. For the same reason, it > is incorrect to call `Run` when using `StdoutPipe`. See the example for > idiomatic usage. See: https://pkg.go.dev/os/exec#example-Cmd.StdoutPipe
1 parent e486e84 commit 2275dcf

File tree

1 file changed

+25
-13
lines changed

1 file changed

+25
-13
lines changed

internal/batches/executor/run_steps.go

Lines changed: 25 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -313,8 +313,6 @@ func executeSingleStep(
313313
cmd.Dir = *dir
314314
}
315315

316-
var stdoutBuffer, stderrBuffer bytes.Buffer
317-
318316
writerCtx, writerCancel := context.WithCancel(ctx)
319317
defer writerCancel()
320318
uiStdoutWriter := opts.ui.StepStdoutWriter(writerCtx, opts.task, i)
@@ -324,33 +322,47 @@ func executeSingleStep(
324322
uiStderrWriter.Close()
325323
}()
326324

325+
var stdoutBuffer, stderrBuffer bytes.Buffer
327326
stdout := io.MultiWriter(&stdoutBuffer, uiStdoutWriter, opts.logger.PrefixWriter("stdout"))
328327
stderr := io.MultiWriter(&stderrBuffer, uiStderrWriter, opts.logger.PrefixWriter("stderr"))
329328

329+
// Setup readers that pipe the output into the given buffers
330330
wg, err := process.PipeOutput(cmd, stdout, stderr)
331331
if err != nil {
332332
return bytes.Buffer{}, bytes.Buffer{}, errors.Wrap(err, "piping process output")
333333
}
334334

335+
newStepFailedErr := func(wrappedErr error) stepFailedErr {
336+
return stepFailedErr{
337+
Err: wrappedErr,
338+
Args: cmd.Args,
339+
Run: runScript,
340+
Container: step.Container,
341+
TmpFilename: containerTemp,
342+
Stdout: strings.TrimSpace(stdoutBuffer.String()),
343+
Stderr: strings.TrimSpace(stderrBuffer.String()),
344+
}
345+
}
346+
335347
opts.logger.Logf("[Step %d] run: %q, container: %q", i+1, step.Run, step.Container)
336348
opts.logger.Logf("[Step %d] full command: %q", i+1, strings.Join(cmd.Args, " "))
337349

350+
// Start the command
338351
t0 := time.Now()
339-
err = cmd.Run()
352+
if err := cmd.Start(); err != nil {
353+
opts.logger.Logf("[Step %d] error starting Docker container: %+v", i+1, err)
354+
return stdoutBuffer, stderrBuffer, newStepFailedErr(err)
355+
}
356+
357+
// Wait for the readers, because the pipes used by PipeOutput under the
358+
// hood are closed when the command exits
340359
wg.Wait()
360+
// Now wait for the command
361+
err = cmd.Wait()
341362
elapsed := time.Since(t0).Round(time.Millisecond)
342363
if err != nil {
343364
opts.logger.Logf("[Step %d] took %s; error running Docker container: %+v", i+1, elapsed, err)
344-
345-
return stdoutBuffer, stderrBuffer, stepFailedErr{
346-
Err: err,
347-
Args: cmd.Args,
348-
Run: runScript,
349-
Container: step.Container,
350-
TmpFilename: containerTemp,
351-
Stdout: strings.TrimSpace(stdoutBuffer.String()),
352-
Stderr: strings.TrimSpace(stderrBuffer.String()),
353-
}
365+
return stdoutBuffer, stderrBuffer, newStepFailedErr(err)
354366
}
355367

356368
opts.logger.Logf("[Step %d] complete in %s", i+1, elapsed)

0 commit comments

Comments
 (0)