Description
I'm trying to run a process and kill it if it takes too long. So I create a command with a timeout context. Then, when the context expires and the process is not done, it will be killed. This approach works fine unless my Go process is reading from the process's stdout/stderr and the process forks and gives its stdout to its child. Then, cmd.Wait() will not exit until the child is done writing to stdout. I think that is unexpected because once the context expires, cmd.Wait() should unblock.
Here is a minimal example that reproduces my issue:
package main
import (
"bytes"
"context"
"log"
"os/exec"
"time"
)
func main() {
log.SetFlags(0)
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
cmd := exec.CommandContext(ctx, "bash", "-c", "echo -n foo; sleep 5")
stdout := &bytes.Buffer{}
cmd.Stdout = stdout
start := time.Now()
err := cmd.Start()
must(err)
err = cmd.Wait()
cancel()
log.Println("took:", time.Since(start))
log.Println("expected it to take:", time.Second)
log.Println("stdout:", stdout)
log.Println("wait error:", err)
}
func must(err error) {
if err != nil {
panic(err)
}
}
So what I'm proposing is that the stdout/stderr pipes should be closed once the process dies and the context has expired. This will allow the copying goroutines to exit and cmd.Wait() to unblock after the context expires as expected.