Conditionally wait for children after delivering signal

When signaling children and the signal is SIGKILL wait for children
otherwise conditionally wait for children which are ready to report.

This reaps all children which exited due to the signal sent without
blocking indefinitely.

Also:
* Ignore ignore ECHILD, which means the child has already gone.

Signed-off-by: Steven Hartland <steven.hartland@multiplay.co.uk>
This commit is contained in:
Steven Hartland 2017-01-26 13:10:16 +00:00
parent 27a5447ea4
commit 82d895fbb9
1 changed files with 58 additions and 6 deletions

View File

@ -12,6 +12,7 @@ import (
"strconv"
"strings"
"syscall"
"unsafe"
"github.com/Sirupsen/logrus"
"github.com/opencontainers/runc/libcontainer/cgroups"
@ -393,9 +394,50 @@ func setOomScoreAdj(oomScoreAdj int, pid int) error {
return ioutil.WriteFile(path, []byte(strconv.Itoa(oomScoreAdj)), 0600)
}
const _P_PID = 1
type siginfo struct {
si_signo int32
si_errno int32
si_code int32
// below here is a union; si_pid is the only field we use
si_pid int32
// Pad to 128 bytes as detailed in blockUntilWaitable
pad [96]byte
}
// isWaitable returns true if the process has exited false otherwise.
// Its based off blockUntilWaitable in src/os/wait_waitid.go
func isWaitable(pid int) (bool, error) {
si := &siginfo{}
_, _, e := syscall.Syscall6(syscall.SYS_WAITID, _P_PID, uintptr(pid), uintptr(unsafe.Pointer(si)), syscall.WEXITED|syscall.WNOWAIT|syscall.WNOHANG, 0, 0)
if e != 0 {
return false, os.NewSyscallError("waitid", e)
}
return si.si_pid != 0, nil
}
// isNoChildren returns true if err represents a syscall.ECHILD false otherwise
func isNoChildren(err error) bool {
switch err := err.(type) {
case syscall.Errno:
if err == syscall.ECHILD {
return true
}
case *os.SyscallError:
if err.Err == syscall.ECHILD {
return true
}
}
return false
}
// signalAllProcesses freezes then iterates over all the processes inside the
// manager's cgroups sending the signal s to them.
// If s is SIGKILL then it will also wait for each process to exit.
// If s is SIGKILL then it will wait for each process to exit.
// For all other signals it will check if the process is ready to report its
// exit status and only if it is will a wait be performed.
func signalAllProcesses(m cgroups.Manager, s os.Signal) error {
var procs []*os.Process
if err := m.Freeze(configs.Frozen); err != nil {
@ -421,13 +463,23 @@ func signalAllProcesses(m cgroups.Manager, s os.Signal) error {
logrus.Warn(err)
}
if s != syscall.SIGKILL {
return nil
}
for _, p := range procs {
if s != syscall.SIGKILL {
if ok, err := isWaitable(p.Pid); err != nil {
if !isNoChildren(err) {
logrus.Warn("signalAllProcesses: ", p.Pid, err)
}
continue
} else if !ok {
// Not ready to report so don't wait
continue
}
}
if _, err := p.Wait(); err != nil {
logrus.Warn(err)
if !isNoChildren(err) {
logrus.Warn("wait: ", err)
}
}
}
return nil