Skip to content

Commit b788e91

Browse files
committed
os: always return syscall.ECHILD from Wait for done process
For processes that don't exist at lookup time, CL 570036 and CL 588675 make Wait return unconditionally return ErrProcessDone when using pidfd, rather than attempting to make a wait system call. This is consistent with Signal/Kill, but inconsistent with the previous behavior of Wait, which would pass through the kernel error, syscall.ECHILD. Switch the ErrProcessDone case to return syscall.ECHILD instead for consistency with previous behavior. That said, I do think a future release should consider changing ECHILD to ErrProcessDone in all cases (including when making an actual wait system call) for better consistency with Signal/Kill/FindProcess. Fixes #67926. Cq-Include-Trybots: luci.golang.try:gotip-darwin-amd64_14,gotip-solaris-amd64,gotip-openbsd-amd64 Change-Id: I1f688a5751d0f3aecea99c3a5b35c7894cfc2beb Reviewed-on: https://go-review.googlesource.com/c/go/+/591816 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Kirill Kolyshkin <[email protected]> Reviewed-by: Cherry Mui <[email protected]>
1 parent 4b8f807 commit b788e91

File tree

4 files changed

+37
-28
lines changed

4 files changed

+37
-28
lines changed

src/os/exec_test.go

-26
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ package os_test
66

77
import (
88
"internal/testenv"
9-
"math"
109
"os"
1110
"os/signal"
1211
"runtime"
@@ -76,28 +75,3 @@ func TestProcessReleaseTwice(t *testing.T) {
7675
t.Fatalf("second Release: got err %v, want %v", err, want)
7776
}
7877
}
79-
80-
// Lookup of a process that does not exist at time of lookup.
81-
func TestProcessAlreadyDone(t *testing.T) {
82-
if runtime.GOOS == "windows" {
83-
t.Skip("Windows does not support lookup of non-existant process")
84-
}
85-
if runtime.GOARCH == "wasm" {
86-
t.Skip("Wait not supported om wasm port")
87-
}
88-
89-
// Theoretically MaxInt32 is a valid PID, but the chance of it actually
90-
// being used is extremely unlikely.
91-
p, err := os.FindProcess(math.MaxInt32)
92-
if err != nil {
93-
t.Fatalf("FindProcess(math.MaxInt32) got err %v, want nil", err)
94-
}
95-
96-
if ps, err := p.Wait(); err != os.ErrProcessDone {
97-
t.Errorf("Wait() got err %v (ps %+v), want ErrProcessDone", err, ps)
98-
}
99-
100-
if err := p.Release(); err != nil {
101-
t.Errorf("Release() got err %v, want nil", err)
102-
}
103-
}

src/os/exec_unix_test.go

+30
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,11 @@
77
package os_test
88

99
import (
10+
"errors"
1011
"internal/testenv"
12+
"math"
1113
. "os"
14+
"runtime"
1215
"syscall"
1316
"testing"
1417
)
@@ -27,6 +30,33 @@ func TestErrProcessDone(t *testing.T) {
2730
}
2831
}
2932

33+
// Lookup of a process that does not exist at time of lookup.
34+
func TestProcessAlreadyDone(t *testing.T) {
35+
// Theoretically MaxInt32 is a valid PID, but the chance of it actually
36+
// being used is extremely unlikely.
37+
pid := math.MaxInt32
38+
if runtime.GOOS == "solaris" || runtime.GOOS == "illumos" {
39+
// Solaris/Illumos have a lower limit, above which wait returns
40+
// EINVAL (see waitid in usr/src/uts/common/os/exit.c in
41+
// illumos). This is configurable via sysconf(_SC_MAXPID), but
42+
// we'll just take the default.
43+
pid = 30000-1
44+
}
45+
46+
p, err := FindProcess(pid)
47+
if err != nil {
48+
t.Fatalf("FindProcess(math.MaxInt32) got err %v, want nil", err)
49+
}
50+
51+
if ps, err := p.Wait(); !errors.Is(err, syscall.ECHILD) {
52+
t.Errorf("Wait() got err %v (ps %+v), want %v", err, ps, syscall.ECHILD)
53+
}
54+
55+
if err := p.Release(); err != nil {
56+
t.Errorf("Release() got err %v, want nil", err)
57+
}
58+
}
59+
3060
func TestUNIXProcessAlive(t *testing.T) {
3161
testenv.MustHaveGoBuild(t)
3262
t.Parallel()

src/os/pidfd_linux.go

+4-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,10 @@ func (p *Process) pidfdWait() (*ProcessState, error) {
7474
handle, status := p.handleTransientAcquire()
7575
switch status {
7676
case statusDone:
77-
return nil, ErrProcessDone
77+
// Process already completed Wait, or was not found by
78+
// pidfdFind. Return ECHILD for consistency with what the wait
79+
// syscall would return.
80+
return nil, NewSyscallError("wait", syscall.ECHILD)
7881
case statusReleased:
7982
return nil, syscall.EINVAL
8083
}

src/os/pidfd_linux_test.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
package os_test
66

77
import (
8+
"errors"
89
"internal/testenv"
910
"os"
11+
"syscall"
1012
"testing"
1113
)
1214

@@ -47,7 +49,7 @@ func TestFindProcessViaPidfd(t *testing.T) {
4749
if err := proc.Signal(os.Kill); err != os.ErrProcessDone {
4850
t.Errorf("Signal: got %v, want %v", err, os.ErrProcessDone)
4951
}
50-
if _, err := proc.Wait(); err != os.ErrProcessDone {
52+
if _, err := proc.Wait(); !errors.Is(err, syscall.ECHILD) {
5153
t.Errorf("Wait: got %v, want %v", err, os.ErrProcessDone)
5254
}
5355
// Release never returns errors on Unix.

0 commit comments

Comments
 (0)