package master import ( "analysis/logo" "os" "os/signal" "syscall" ) // ExitInfo info type ExitInfo struct { Pid int ExitCode int } // Config config type Config struct { Pid int Options int DisablePid1Check bool } // Handle death of child (SIGCHLD) messages. Pushes the signal onto the // notifications channel if there is a waiter. func sigChildHandler(notifications chan os.Signal) { sigs := make(chan os.Signal, 3) signal.Notify(sigs, syscall.SIGCHLD) signal.Ignore(syscall.SIGPIPE) for { sig := <-sigs select { case notifications <- sig: /* published it. */ default: /* * Notifications channel full - drop it to the * floor. This ensures we don't fill up the SIGCHLD * queue. The reaper just waits for any child * process (pid=-1), so we ain't loosing it!! ;^) */ } } } /* End of function sigChildHandler. */ // Be a good parent - clean up behind the children. func reapChildren(config Config, info chan<- ExitInfo) { notifications := make(chan os.Signal, 1) go sigChildHandler(notifications) pid := config.Pid opts := config.Options for { sig := <-notifications logo.Infof(" - Received signal %v\n", sig) for { var wstatus syscall.WaitStatus /* * Reap 'em, so that zombies don't accumulate. * Plants vs. Zombies!! */ pid, err := syscall.Wait4(pid, &wstatus, opts, nil) info <- ExitInfo{pid, wstatus.ExitStatus()} for syscall.EINTR == err { pid, err = syscall.Wait4(pid, &wstatus, opts, nil) info <- ExitInfo{pid, wstatus.ExitStatus()} } if syscall.ECHILD == err { break } logo.Infof(" - Grim reaper cleanup: pid=%d, wstatus=%+v\n", pid, wstatus) } } } /* End of function reapChildren. */ /* * ====================================================================== * Section: Exported functions * ====================================================================== */ // Normal entry point for the reaper code. Start reaping children in the // background inside a goroutine. // Reap reap func Reap(info chan<- ExitInfo) { /* * Only reap processes if we are taking over init's duties aka * we are running as pid 1 inside a docker container. The default * is to reap all processes. */ Start(Config{ Pid: -1, Options: 0, DisablePid1Check: true, }, info) } /* End of [exported] function Reap. */ // Entry point for invoking the reaper code with a specific configuration. // The config allows you to bypass the pid 1 checks, so handle with care. // The child processes are reaped in the background inside a goroutine. // Start start func Start(config Config, info chan<- ExitInfo) { /* * Start the Reaper with configuration options. This allows you to * reap processes even if the current pid isn't running as pid 1. * So ... use with caution!! * * In most cases, you are better off just using Reap() as that * checks if we are running as Pid 1. */ if !config.DisablePid1Check { mypid := os.Getpid() if 1 != mypid { logo.Errorln(" - Grim reaper disabled, pid not 1\n") return } } /* * Ok, so either pid 1 checks are disabled or we are the grandma * of 'em all, either way we get to play the grim reaper. * You will be missed, Terry Pratchett!! RIP */ go reapChildren(config, info) } /* End of [exported] function Start. */