-
Notifications
You must be signed in to change notification settings - Fork 18.8k
Description
What version of Go are you using (go version)?
$ go version go version go1.13.6 linux/amd64
Does this issue reproduce with the latest release?
Yes — tried with go1.14rc1
What operating system and processor architecture are you using (go env)?
go env Output
$ go env GO111MODULE="" GOARCH="amd64" GOBIN="" GOCACHE="/home/markse/.cache/go-build" GOENV="/home/markse/.config/go/env" GOEXE="" GOFLAGS="" GOHOSTARCH="amd64" GOHOSTOS="linux" GONOPROXY="" GONOSUMDB="" GOOS="linux" GOPATH="/home/markse" GOPRIVATE="" GOPROXY="https://proxy.golang.org,direct" GOROOT="/usr/local/go" GOSUMDB="sum.golang.org" GOTMPDIR="" GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64" GCCGO="gccgo" AR="ar" CC="gcc" CXX="g++" CGO_ENABLED="1" GOMOD="" CGO_CFLAGS="-g -O2" CGO_CPPFLAGS="" CGO_CXXFLAGS="-g -O2" CGO_FFLAGS="-g -O2" CGO_LDFLAGS="-g -O2" PKG_CONFIG="pkg-config" GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build088413182=/tmp/go-build -gno-record-gcc-switches"
What did you do?
package main
import (
"os"
"os/exec"
// "os/signal"
"syscall"
)
func main() {
// This does make it work, but shouldn't be necessary and causes side-effects
// signal.Ignore(syscall.SIGTTIN, syscall.SIGTTOU)
cmd := exec.Command("echo", "hello")
cmd.SysProcAttr = &syscall.SysProcAttr{
Foreground: true,
}
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
cmd.Run()
}What did you expect to see?
The process should execute echo and display hello.
What did you see instead?
The process hangs.
ps shows the child process as suspended and doesn't appear to have called execve yet:
$ ps j
PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND
…
6072 25553 25553 6072 pts/0 25553 Dl+ 1000 0:00 ./foreground
25553 25558 25558 6072 pts/0 25553 T 1000 0:00 ./foreground
…Explanation
From what we can determine, we believe this is what is happening:
- The process is forked (ref)
- The child calls
setpgid/SYS_SETPGID, creating a new background process group (ref) - The child calls
tcsetpgrp/TIOCSPGRP, causing the process to be suspended (ref)
While a process in the foreground process group is allowed to call tcsetpgrp, a process in a background process group has an additional hurdle. From the man page for tcsetpgrp:
If
tcsetpgrp()is called by a member of a background process group in its session, and the calling process is not blocking or ignoringSIGTTOU, aSIGTTOUsignal is sent to all members of this background process group.
Which explains why the process is getting suspended, as the default action for SIGTTOU is to stop the process. This also explains when ignoring SIGTTOU before spawning the process fixes the problem. However, this has an undesired side-effect: the subsequently exec'd process then ignores SIGTTOU as well.
Conclusion
Unfortunately, because all of this work is done in the child fork, there is no way for Go user code to reset the ignored SIGTTOU before exec'ing the process.
So there is no way to have a user ignore the SIGTTOU signal without an adverse effect on the child process.
Proposal: if the SIGTTOU signal could be ignored for the duration of the tcsetpgrp/TIOCSPGRP call and restored to its prior state immediately following, we believe this would elicit the expected behavior without adverse side-effects.