go: os/exec: exec.Cmd fails to cancel with non-*os.File outputs on linux
go1.7, go1.8rc3
When using a Context to cancel an exec.Cmd, if either of the outputs are an io.Writer which isn’t an *os.File the command will fail to terminate.
The following panics on amd64 linux
ctx, cancel := context.WithCancel(context.Background())
cmd := exec.CommandContext(ctx, "/bin/sh", "-c", "sleep 60")
var output bytes.Buffer
cmd.Stdout = &output
cmd.Start()
log.Println("started command")
done := make(chan error)
go func() {
done <- cmd.Wait()
}()
time.Sleep(50 * time.Millisecond)
log.Println("canceling command")
cancel()
select {
case err := <-done:
log.Println("done:", err)
case <-time.After(time.Second):
panic("failed to cancel")
The full stack trace is:
main.main()
/home/user/command_context.go:35 +0x499
goroutine 5 [syscall]:
syscall.Syscall(0x0, 0x4, 0xc420092000, 0x200, 0x7f15a35f4000, 0x19, 0xc420092000)
/usr/local/go/src/syscall/asm_linux_amd64.s:18 +0x5
syscall.read(0x4, 0xc420092000, 0x200, 0x200, 0xc420026400, 0x7f15a35f4000, 0x0)
/usr/local/go/src/syscall/zsyscall_linux_amd64.go:783 +0x55
syscall.Read(0x4, 0xc420092000, 0x200, 0x200, 0x425b5f, 0x4dd550, 0xc420023dc0)
/usr/local/go/src/syscall/syscall_unix.go:162 +0x49
os.(*File).read(0xc42000c038, 0xc420092000, 0x200, 0x200, 0x4602ab, 0x4b38e0, 0x200)
/usr/local/go/src/os/file_unix.go:165 +0x4f
os.(*File).Read(0xc42000c038, 0xc420092000, 0x200, 0x200, 0x0, 0x7f15a3598028, 0xc420023e60)
/usr/local/go/src/os/file.go:101 +0x76
bytes.(*Buffer).ReadFrom(0xc420048070, 0x5381e0, 0xc42000c038, 0x7f15a3598028, 0xc420048070, 0x1)
/usr/local/go/src/bytes/buffer.go:179 +0x160
io.copyBuffer(0x538120, 0xc420048070, 0x5381e0, 0xc42000c038, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0)
/usr/local/go/src/io/io.go:384 +0x2cb
io.Copy(0x538120, 0xc420048070, 0x5381e0, 0xc42000c038, 0x0, 0x0, 0x0)
/usr/local/go/src/io/io.go:360 +0x68
os/exec.(*Cmd).writerDescriptor.func1(0x0, 0x0)
/usr/local/go/src/os/exec/exec.go:254 +0x4d
os/exec.(*Cmd).Start.func1(0xc42007e000, 0xc42000a2a0)
/usr/local/go/src/os/exec/exec.go:371 +0x27
created by os/exec.(*Cmd).Start
/usr/local/go/src/os/exec/exec.go:372 +0x4e4
goroutine 7 [chan receive]:
os/exec.(*Cmd).Wait(0xc42007e000, 0x0, 0x0)
/usr/local/go/src/os/exec/exec.go:443 +0x118
main.main.func1(0xc4200681e0, 0xc42007e000)
/home/user/command_context.go:24 +0x2b
created by main.main
/home/user/command_context.go:25 +0x1eb
exit status 2
About this issue
- Original URL
- State: closed
- Created 7 years ago
- Reactions: 4
- Comments: 31 (21 by maintainers)
Commits related to this issue
- os/exec: clarify Cmd.Wait documentation a bit more explicitly It already implied that Cmd.Wait is more than os.Process.Wait, but say so explicitly. See https://github.com/golang/go/issues/18874#issu... — committed to golang/go by bradfitz 7 years ago
- Make use of bash directly instead of dash I suspect Go is calling `/bin/sh` when using os.exec Command, but this will spawn `dash` on LX zones. Incorrect behavior expected as shown on https://github.... — committed to virtua-network/smartos_exporter by deleted user 7 years ago
- fails: work around golang/go/issues/18874 — committed to FuzzyMonkeyCo/monkey by fenollp 7 years ago
- fails: work around golang/go#18874 (#21) * fails: work around golang/go/issues/18874 * fails: have curl output server errors — committed to FuzzyMonkeyCo/monkey by fenollp 7 years ago
- Fork stdlibs os/exec package It was copied from my local go version at: go version go1.9.2 linux/amd64 This is so that we can export a helper to close IO explicitly when we need it. For more info, s... — committed to pachyderm/pachyderm by sjezewski 7 years ago
- improve timeout handling implement workaround for https://github.com/golang/go/issues/18874 — committed to ConSol-Monitoring/mod-gearman-worker-go by sni 6 years ago
I’m inclined to close this as working as expected. You are asking for the processes’s standard output to be collected. That means that the
Waitmethod will block until the process closes its standard output. In the case that fails, the child process still has standard output open. If you want something else to happen, you need to ensure that you kill not just the child, but any other process it starts that may keep standard output open.