Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion internal/util/process.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
package util

import (
"errors"
"os"
"strings"
"syscall"
Expand All @@ -23,7 +24,13 @@ func IsProcessAlive(pid int) bool {
if err != nil {
return false
}
if proc.Signal(syscall.Signal(0)) != nil {
// #2190: kill(pid, 0) EPERM means the process EXISTS but we lack
// permission to signal it (POSIX) - treating it as dead mirrors the
// exact bug Windows fixed in #1723 (a privileged daemon probed by an
// unprivileged caller: false-dead deletes the PID file and forks a
// second instance, or triggers concurrent crash recovery - the Unix
// twin of #1490). Only a real error (ESRCH et al.) means dead.
if err := proc.Signal(syscall.Signal(0)); err != nil && !errors.Is(err, syscall.EPERM) {
return false
}
return !isZombieUnix(pid)
Expand Down
41 changes: 41 additions & 0 deletions internal/util/zz_issue2190_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
//go:build !windows

package util

// #2190 regression: kill(pid,0) EPERM means the process EXISTS but is
// not signalable by us (POSIX) - it was returned as dead, the Unix twin
// of the #1723 Windows bug (false-dead deletes PID files and forks
// second daemons / concurrent crash recovery). The EPERM branch itself
// needs a cross-privilege process (not unit-testable); these pin the
// surrounding semantics that must not regress alongside it.

import (
"os"
"syscall"
"testing"
)

func TestIsProcessAliveBasicSemantics(t *testing.T) {
if !IsProcessAlive(os.Getpid()) {
t.Fatal("self must be alive")
}
// A definitely-dead PID: spawn and reap one.
r, w, err := os.Pipe()
if err != nil {
t.Skipf("pipe: %v", err)
}
defer r.Close()
defer w.Close()
pid, err := syscall.ForkExec("/bin/true", []string{"/bin/true"}, &syscall.ProcAttr{Files: []uintptr{r.Fd(), w.Fd(), w.Fd()}})
if err != nil {
t.Skipf("forkexec: %v", err)
}
// Wait for exit; probe until dead (bounded).
for i := 0; i < 100; i++ {
if !IsProcessAlive(pid) {
return // dead, as expected
}
syscall.Wait4(pid, nil, syscall.WNOHANG, nil)
}
t.Fatal("reaped child must be reported dead")
}
Loading