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

Most upvoted comments

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 Wait method 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.