Skip to content

Commit 14ffe54

Browse files
committed
os/exec: use pidfd for waiting and signaling of processes
Using pidfd allows us to have a handle on the process and poll the handle to non-blocking wait for the process to exit. Fixes #34396 Fixes #60321 Fixes #60320
1 parent f90b4cd commit 14ffe54

File tree

5 files changed

+141
-63
lines changed

5 files changed

+141
-63
lines changed

src/os/exec.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ func Getppid() int { return syscall.Getppid() }
8585
// The Process it returns can be used to obtain information
8686
// about the underlying operating system process.
8787
//
88-
// On Unix systems, FindProcess always succeeds and returns a Process
88+
// On non-Linux Unix systems, FindProcess always succeeds and returns a Process
8989
// for the given pid, regardless of whether the process exists.
9090
func FindProcess(pid int) (*Process, error) {
9191
return findProcess(pid)

src/os/exec_posix.go

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -69,9 +69,9 @@ func (p *Process) kill() error {
6969

7070
// ProcessState stores information about a process, as reported by Wait.
7171
type ProcessState struct {
72-
pid int // The process's id.
73-
status syscall.WaitStatus // System-dependent status info.
74-
rusage *syscall.Rusage
72+
pid int // The process's id.
73+
siginfo Siginfo // System-dependent status info.
74+
rusage *syscall.Rusage
7575
}
7676

7777
// Pid returns the process id of the exited process.
@@ -80,15 +80,15 @@ func (p *ProcessState) Pid() int {
8080
}
8181

8282
func (p *ProcessState) exited() bool {
83-
return p.status.Exited()
83+
return p.siginfo.Exited()
8484
}
8585

8686
func (p *ProcessState) success() bool {
87-
return p.status.ExitStatus() == 0
87+
return p.siginfo.ExitStatus() == 0
8888
}
8989

9090
func (p *ProcessState) sys() any {
91-
return p.status
91+
return p.siginfo
9292
}
9393

9494
func (p *ProcessState) sysUsage() any {
@@ -99,27 +99,27 @@ func (p *ProcessState) String() string {
9999
if p == nil {
100100
return "<nil>"
101101
}
102-
status := p.Sys().(syscall.WaitStatus)
102+
siginfo := p.Sys().(Siginfo)
103103
res := ""
104104
switch {
105-
case status.Exited():
106-
code := status.ExitStatus()
105+
case siginfo.Exited():
106+
code := siginfo.ExitStatus()
107107
if runtime.GOOS == "windows" && uint(code) >= 1<<16 { // windows uses large hex numbers
108108
res = "exit status " + uitox(uint(code))
109109
} else { // unix systems use small decimal integers
110110
res = "exit status " + itoa.Itoa(code) // unix
111111
}
112-
case status.Signaled():
113-
res = "signal: " + status.Signal().String()
114-
case status.Stopped():
115-
res = "stop signal: " + status.StopSignal().String()
116-
if status.StopSignal() == syscall.SIGTRAP && status.TrapCause() != 0 {
117-
res += " (trap " + itoa.Itoa(status.TrapCause()) + ")"
112+
case siginfo.Signaled():
113+
res = "signal: " + siginfo.Signal().String()
114+
case siginfo.Stopped():
115+
res = "stop signal: " + siginfo.StopSignal().String()
116+
if siginfo.Trapped() && siginfo.TrapCause() != 0 {
117+
res += " (trap " + itoa.Itoa(siginfo.TrapCause()) + ")"
118118
}
119-
case status.Continued():
119+
case siginfo.Continued():
120120
res = "continued"
121121
}
122-
if status.CoreDump() {
122+
if siginfo.CoreDump() {
123123
res += " (core dumped)"
124124
}
125125
return res
@@ -132,5 +132,5 @@ func (p *ProcessState) ExitCode() int {
132132
if p == nil {
133133
return -1
134134
}
135-
return p.status.ExitStatus()
135+
return p.siginfo.ExitStatus()
136136
}

src/os/exec_unix.go

Lines changed: 93 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,64 @@ import (
1111
"runtime"
1212
"syscall"
1313
"time"
14+
"unsafe"
1415
)
1516

17+
const CLD_EXITED = 1
18+
const CLD_KILLED = 2
19+
const CLD_DUMPED = 3
20+
const CLD_TRAPPED = 4
21+
const CLD_STOPPED = 5
22+
const CLD_CONTINUED = 6
23+
24+
type Siginfo struct {
25+
Signo int32
26+
Errno int32
27+
Code int32
28+
_ int32
29+
_ [112]byte
30+
}
31+
32+
func (s Siginfo) Exited() bool { return s.Code == CLD_EXITED }
33+
34+
func (s Siginfo) Signaled() bool { return s.Code == CLD_KILLED }
35+
36+
func (s Siginfo) Stopped() bool { return s.Code == CLD_STOPPED }
37+
38+
func (s Siginfo) Trapped() bool { return s.Code == CLD_TRAPPED }
39+
40+
func (s Siginfo) Continued() bool { return s.Code == CLD_CONTINUED }
41+
42+
func (s Siginfo) CoreDump() bool { return s.Code == CLD_DUMPED }
43+
44+
func (s Siginfo) ExitStatus() int {
45+
if !s.Exited() {
46+
return -1
47+
}
48+
return int(s.Errno)
49+
}
50+
51+
func (s Siginfo) Signal() syscall.Signal {
52+
if !s.Signaled() {
53+
return -1
54+
}
55+
return syscall.Signal(s.Errno)
56+
}
57+
58+
func (s Siginfo) StopSignal() syscall.Signal {
59+
if !s.Signaled() {
60+
return -1
61+
}
62+
return syscall.Signal(s.Errno)
63+
}
64+
65+
func (s Siginfo) TrapCause() int {
66+
if !s.Trapped() {
67+
return -1
68+
}
69+
return int(s.Errno)
70+
}
71+
1672
func (p *Process) wait() (ps *ProcessState, err error) {
1773
if p.Pid == -1 {
1874
return nil, syscall.EINVAL
@@ -34,27 +90,36 @@ func (p *Process) wait() (ps *ProcessState, err error) {
3490
}
3591

3692
var (
37-
status syscall.WaitStatus
38-
rusage syscall.Rusage
39-
pid1 int
40-
e error
93+
siginfo Siginfo
94+
rusage syscall.Rusage
95+
e syscall.Errno
4196
)
4297
for {
43-
pid1, e = syscall.Wait4(p.Pid, &status, 0, &rusage)
44-
if e != syscall.EINTR {
98+
_, _, e = syscall.Syscall6(syscall.SYS_WAITID, _P_PIDFD, p.handle, uintptr(unsafe.Pointer(&siginfo)), syscall.WEXITED, uintptr(unsafe.Pointer(&rusage)), 0)
99+
if e == syscall.EINTR {
100+
continue
101+
} else if e == syscall.ENOSYS {
102+
// waitid has been available since Linux 2.6.9, but
103+
// reportedly is not available in Ubuntu on Windows.
104+
// See issue 16610.
105+
panic("TODO: Implement fallback")
106+
} else if e != 0 {
107+
break
108+
}
109+
// During ptrace the wait might return also for non-exit reasons. In that case we retry.
110+
// See: https://lwn.net/Articles/688624/
111+
if siginfo.Exited() || siginfo.Signaled() || siginfo.CoreDump() {
45112
break
46113
}
47114
}
48-
if e != nil {
49-
return nil, NewSyscallError("wait", e)
50-
}
51-
if pid1 != 0 {
52-
p.setDone()
115+
runtime.KeepAlive(p)
116+
if e != 0 {
117+
return nil, NewSyscallError("waitid", e)
53118
}
54119
ps = &ProcessState{
55-
pid: pid1,
56-
status: status,
57-
rusage: &rusage,
120+
pid: p.Pid,
121+
siginfo: siginfo,
122+
rusage: &rusage,
58123
}
59124
return ps, nil
60125
}
@@ -75,26 +140,35 @@ func (p *Process) signal(sig Signal) error {
75140
if !ok {
76141
return errors.New("os: unsupported signal type")
77142
}
78-
if e := syscall.Kill(p.Pid, s); e != nil {
143+
if _, _, e := syscall.RawSyscall6(syscall.SYS_PIDFD_SEND_SIGNAL, p.handle, uintptr(s), 0, 0, 0, 0); e != 0 {
79144
if e == syscall.ESRCH {
80145
return ErrProcessDone
81146
}
82-
return e
147+
return NewSyscallError("pidfd_send_signal", e)
83148
}
149+
runtime.KeepAlive(p)
84150
return nil
85151
}
86152

87153
func (p *Process) release() error {
88-
// NOOP for unix.
154+
e := syscall.Close(int(p.handle))
155+
if e != nil {
156+
return NewSyscallError("close", e)
157+
158+
}
89159
p.Pid = -1
90160
// no need for a finalizer anymore
91161
runtime.SetFinalizer(p, nil)
92162
return nil
93163
}
94164

95165
func findProcess(pid int) (p *Process, err error) {
96-
// NOOP for unix.
97-
return newProcess(pid, 0), nil
166+
fd, _, e := syscall.Syscall(syscall.SYS_PIDFD_OPEN, uintptr(pid), 0, 0)
167+
runtime.KeepAlive(p)
168+
if e != 0 {
169+
return nil, NewSyscallError("pidfd_open", e)
170+
}
171+
return newProcess(pid, fd), nil
98172
}
99173

100174
func (p *ProcessState) userTime() time.Duration {

src/os/wait_waitid.go

Lines changed: 17 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,39 +10,32 @@
1010
package os
1111

1212
import (
13-
"runtime"
13+
"internal/poll"
1414
"syscall"
15-
"unsafe"
1615
)
1716

18-
const _P_PID = 1
17+
const _P_PIDFD = 3
1918

2019
// blockUntilWaitable attempts to block until a call to p.Wait will
2120
// succeed immediately, and reports whether it has done so.
2221
// It does not actually call p.Wait.
2322
func (p *Process) blockUntilWaitable() (bool, error) {
24-
// The waitid system call expects a pointer to a siginfo_t,
25-
// which is 128 bytes on all Linux systems.
26-
// On darwin/amd64, it requires 104 bytes.
27-
// We don't care about the values it returns.
28-
var siginfo [16]uint64
29-
psig := &siginfo[0]
30-
var e syscall.Errno
31-
for {
32-
_, _, e = syscall.Syscall6(syscall.SYS_WAITID, _P_PID, uintptr(p.Pid), uintptr(unsafe.Pointer(psig)), syscall.WEXITED|syscall.WNOWAIT, 0, 0)
33-
if e != syscall.EINTR {
34-
break
35-
}
23+
fd := poll.FD{
24+
Sysfd: int(p.handle),
3625
}
37-
runtime.KeepAlive(p)
38-
if e != 0 {
39-
// waitid has been available since Linux 2.6.9, but
40-
// reportedly is not available in Ubuntu on Windows.
41-
// See issue 16610.
42-
if e == syscall.ENOSYS {
43-
return false, nil
44-
}
45-
return false, NewSyscallError("waitid", e)
26+
err := fd.Init("pidfd", false)
27+
if err != nil {
28+
return false, err
29+
}
30+
// We just want to make sure fd is ready for reading, but that is not yet available.
31+
// See: https://github.com/golang/go/issues/15735
32+
buf := make([]byte, 1)
33+
_, err = fd.Read(buf)
34+
if err == syscall.EINVAL {
35+
// fd is ready for reading, but reading failed, as expected on pidfd.
36+
return true, nil
37+
} else if err != nil {
38+
return false, err
4639
}
4740
return true, nil
4841
}

src/syscall/exec_unix.go

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,9 @@ import (
1616
"unsafe"
1717
)
1818

19+
const SYS_PIDFD_OPEN = 434
20+
const SYS_PIDFD_SEND_SIGNAL = 424
21+
1922
// ForkLock is used to synchronize creation of new file descriptors
2023
// with fork.
2124
//
@@ -332,7 +335,15 @@ func ForkExec(argv0 string, argv []string, attr *ProcAttr) (pid int, err error)
332335
// StartProcess wraps ForkExec for package os.
333336
func StartProcess(argv0 string, argv []string, attr *ProcAttr) (pid int, handle uintptr, err error) {
334337
pid, err = forkExec(argv0, argv, attr)
335-
return pid, 0, err
338+
if err != nil {
339+
return
340+
}
341+
fd, _, e := Syscall(SYS_PIDFD_OPEN, uintptr(pid), 0, 0)
342+
if e != 0 {
343+
err = errnoErr(e)
344+
return
345+
}
346+
return pid, fd, err
336347
}
337348

338349
// Implemented in runtime package.

0 commit comments

Comments
 (0)