diff --git a/pkg/proc/reaper_unsupported.go b/pkg/proc/proc.go similarity index 76% rename from pkg/proc/reaper_unsupported.go rename to pkg/proc/proc.go index 75644fa5a8..c9491af299 100644 --- a/pkg/proc/reaper_unsupported.go +++ b/pkg/proc/proc.go @@ -4,5 +4,5 @@ package proc // StartReaper has no effect on non-linux platforms. // Support for other unices will be added. -func StartReaper() { +func StartReaper(period time.Duration) { } diff --git a/pkg/proc/proc_linux.go b/pkg/proc/proc_linux.go new file mode 100644 index 0000000000..df77fbc71b --- /dev/null +++ b/pkg/proc/proc_linux.go @@ -0,0 +1,82 @@ +package proc + +import ( + "bufio" + "io/ioutil" + "os" + "path/filepath" + "strconv" + "strings" + "syscall" + "time" + + "k8s.io/klog" +) + +// parseProcForZombies parses the current procfs mounted at /proc +// to find processes in the zombie state. +func parseProcForZombies() ([]int, error) { + files, err := ioutil.ReadDir("/proc") + if err != nil { + return nil, err + } + + var zombies []int + for _, file := range files { + processID, err := strconv.Atoi(file.Name()) + if err != nil { + break + } + stateFilePath := filepath.Join("/proc", file.Name(), "status") + fd, err := os.Open(stateFilePath) + if err != nil { + klog.Errorf("Failed to open %v for getting process status: %v", stateFilePath, err) + continue + } + defer fd.Close() + fs := bufio.NewScanner(fd) + for fs.Scan() { + line := fs.Text() + if strings.HasPrefix(line, "State:") { + if strings.Contains(line, "zombie") { + zombies = append(zombies, processID) + } + break + } + } + } + + return zombies, nil +} + +// StartReaper starts a goroutine to reap processes periodically if called +// from a pid 1 process. +// If period is 0, then it is defaulted to 5 seconds. +// A caller can adjust the period depending on how many and how frequently zombie +// processes are created and need to be reaped. +func StartReaper(period time.Duration) { + if os.Getpid() == 1 { + const defaultReaperPeriodSeconds = 5 + if period == 0 { + period = defaultReaperPeriodSeconds * time.Second + } + go func() { + var zs []int + var err error + for { + zs, err = parseProcForZombies() + if err != nil { + klog.Errorf("Failed to parse proc filesystem to find processes to reap: %v", err) + continue + } + time.Sleep(period) + for _, z := range zs { + cpid, err := syscall.Wait4(z, nil, syscall.WNOHANG, nil) + if err != nil { + klog.Errorf("Unable to reap process pid %v: %v", cpid, err) + } + } + } + }() + } +} diff --git a/pkg/proc/reaper.go b/pkg/proc/reaper.go deleted file mode 100644 index 21f5f71ff5..0000000000 --- a/pkg/proc/reaper.go +++ /dev/null @@ -1,37 +0,0 @@ -// +build linux - -package proc - -import ( - "os" - "os/signal" - "syscall" - - "k8s.io/klog" -) - -// StartReaper starts a goroutine to reap processes if called from a process -// that has pid 1. -func StartReaper() { - if os.Getpid() == 1 { - klog.V(4).Infof("Launching reaper") - go func() { - sigs := make(chan os.Signal, 1) - signal.Notify(sigs, syscall.SIGCHLD) - for { - // Wait for a child to terminate - sig := <-sigs - klog.V(4).Infof("Signal received: %v", sig) - for { - // Reap processes - cpid, _ := syscall.Wait4(-1, nil, syscall.WNOHANG, nil) - if cpid < 1 { - break - } - - klog.V(4).Infof("Reaped process with pid %d", cpid) - } - } - }() - } -}