Skip to content

Commit dbe2e75

Browse files
kolyshkinprattmic
authored andcommitted
os: make FindProcess use pidfd on Linux
This is a continuation of CL 570036. Amend FindProcess to use pidfdFind, and make it return a special Process with Pid of pidDone (-2) if the process is not found. Amend Wait and Signal to return ErrProcessDone if pid == pidDone. The alternative to the above would be to make FindProcess return ErrProcessDone, but this is unexpected and incompatible API change, as discussed in #65866 and #51246. For #62654. Rework of CL 542699 (which got reverted in CL 566476). Change-Id: Ifb4cd3ad1433152fd72ee685d0b85d20377f8723 Reviewed-on: https://go-review.googlesource.com/c/go/+/570681 TryBot-Bypass: Dmitri Shuralyov <[email protected]> Run-TryBot: Kirill Kolyshkin <[email protected]> LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Michael Pratt <[email protected]> Reviewed-by: Dmitri Shuralyov <[email protected]>
1 parent 0bc684a commit dbe2e75

6 files changed

+92
-14
lines changed

src/os/exec_unix.go

Lines changed: 28 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,19 @@ import (
1313
"time"
1414
)
1515

16+
const (
17+
// Special values for Process.Pid.
18+
pidUnset = 0
19+
pidReleased = -1
20+
pidDone = -2
21+
)
22+
1623
func (p *Process) wait() (ps *ProcessState, err error) {
17-
if p.Pid == -1 {
24+
switch p.Pid {
25+
case pidDone:
26+
return nil, ErrProcessDone
27+
case pidReleased:
28+
// Process already released.
1829
return nil, syscall.EINVAL
1930
}
2031
// Wait on pidfd if possible; fallback to using pid on ENOSYS.
@@ -67,10 +78,12 @@ func (p *Process) wait() (ps *ProcessState, err error) {
6778
}
6879

6980
func (p *Process) signal(sig Signal) error {
70-
if p.Pid == -1 {
81+
switch p.Pid {
82+
case pidDone:
83+
return ErrProcessDone
84+
case pidReleased:
7185
return errors.New("os: process already released")
72-
}
73-
if p.Pid == 0 {
86+
case pidUnset:
7487
return errors.New("os: process not initialized")
7588
}
7689
s, ok := sig.(syscall.Signal)
@@ -98,15 +111,23 @@ func convertESRCH(err error) error {
98111

99112
func (p *Process) release() error {
100113
p.pidfdRelease()
101-
p.Pid = -1
114+
p.Pid = pidReleased
102115
// no need for a finalizer anymore
103116
runtime.SetFinalizer(p, nil)
104117
return nil
105118
}
106119

107120
func findProcess(pid int) (p *Process, err error) {
108-
// NOOP for unix.
109-
return newProcess(pid, unsetHandle), nil
121+
h, err := pidfdFind(pid)
122+
if err == ErrProcessDone {
123+
// Can't return an error here since users are not expecting it.
124+
// Instead, return a process with Pid=pidDone and let a
125+
// subsequent Signal or Wait call catch that.
126+
return newProcess(pidDone, unsetHandle), nil
127+
}
128+
// Ignore all other errors from pidfdFind, as the callers
129+
// do not expect them, and we can use pid anyway.
130+
return newProcess(pid, h), nil
110131
}
111132

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

src/os/exec_unix_test.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,10 @@ func TestUNIXProcessAlive(t *testing.T) {
3737
}
3838
defer p.Kill()
3939

40-
proc, _ := FindProcess(p.Pid)
40+
proc, err := FindProcess(p.Pid)
41+
if err != nil {
42+
t.Errorf("OS reported error for running process: %v", err)
43+
}
4144
err = proc.Signal(syscall.Signal(0))
4245
if err != nil {
4346
t.Errorf("OS reported error for running process: %v", err)

src/os/export_linux_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ var (
99
PollSpliceFile = &pollSplice
1010
GetPollFDAndNetwork = getPollFDAndNetwork
1111
CheckPidfdOnce = checkPidfdOnce
12+
PidDone = pidDone
1213
)

src/os/pidfd_linux.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,18 @@ func getPidfd(sysAttr *syscall.SysProcAttr) uintptr {
4747
return uintptr(*sysAttr.PidFD)
4848
}
4949

50+
func pidfdFind(pid int) (uintptr, error) {
51+
if !pidfdWorks() {
52+
return unsetHandle, syscall.ENOSYS
53+
}
54+
55+
h, err := unix.PidFDOpen(pid, 0)
56+
if err == nil {
57+
return h, nil
58+
}
59+
return unsetHandle, convertESRCH(err)
60+
}
61+
5062
func (p *Process) pidfdRelease() {
5163
// Release pidfd unconditionally.
5264
handle := p.handle.Swap(unsetHandle)

src/os/pidfd_linux_test.go

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,16 +5,53 @@
55
package os_test
66

77
import (
8+
"internal/testenv"
89
"os"
910
"testing"
1011
)
1112

12-
func TestCheckPidfd(t *testing.T) {
13-
// This doesn't test anything, but merely allows to check that pidfd
14-
// is working (and thus being tested) in CI on some platforms.
13+
func TestFindProcessViaPidfd(t *testing.T) {
14+
testenv.MustHaveGoBuild(t)
15+
t.Parallel()
16+
1517
if err := os.CheckPidfdOnce(); err != nil {
16-
t.Log("checkPidfd:", err)
17-
} else {
18-
t.Log("pidfd syscalls work")
18+
// Non-pidfd code paths tested in exec_unix_test.go.
19+
t.Skipf("skipping: pidfd not available: %v", err)
20+
}
21+
22+
p, err := os.StartProcess(testenv.GoToolPath(t), []string{"go"}, &os.ProcAttr{})
23+
if err != nil {
24+
t.Fatalf("starting test process: %v", err)
25+
}
26+
p.Wait()
27+
28+
// Use pid of a non-existing process.
29+
proc, err := os.FindProcess(p.Pid)
30+
// FindProcess should never return errors on Unix.
31+
if err != nil {
32+
t.Fatalf("FindProcess: got error %v, want <nil>", err)
33+
}
34+
// FindProcess should never return nil Process.
35+
if proc == nil {
36+
t.Fatal("FindProcess: got nil, want non-nil")
37+
}
38+
if proc.Pid != os.PidDone {
39+
t.Fatalf("got pid: %v, want %d", proc.Pid, os.PidDone)
40+
}
41+
42+
// Check that all Process' public methods work as expected with
43+
// "done" Process.
44+
if err := proc.Kill(); err != os.ErrProcessDone {
45+
t.Errorf("Kill: got %v, want %v", err, os.ErrProcessDone)
46+
}
47+
if err := proc.Signal(os.Kill); err != os.ErrProcessDone {
48+
t.Errorf("Signal: got %v, want %v", err, os.ErrProcessDone)
49+
}
50+
if _, err := proc.Wait(); err != os.ErrProcessDone {
51+
t.Errorf("Wait: got %v, want %v", err, os.ErrProcessDone)
52+
}
53+
// Release never returns errors on Unix.
54+
if err := proc.Release(); err != nil {
55+
t.Fatalf("Release: got %v, want <nil>", err)
1956
}
2057
}

src/os/pidfd_other.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@ func getPidfd(_ *syscall.SysProcAttr) uintptr {
1616
return unsetHandle
1717
}
1818

19+
func pidfdFind(_ int) (uintptr, error) {
20+
return unsetHandle, syscall.ENOSYS
21+
}
22+
1923
func (p *Process) pidfdRelease() {}
2024

2125
func (_ *Process) pidfdWait() (*ProcessState, error) {

0 commit comments

Comments
 (0)