// +build !windows package reaper import ( "os/exec" "sync" "time" "github.com/containerd/containerd/sys" runc "github.com/containerd/go-runc" "github.com/pkg/errors" ) // ErrNoSuchProcess is returned when the process no longer exists var ErrNoSuchProcess = errors.New("no such process") const bufferSize = 1024 // Reap should be called when the process receives an SIGCHLD. Reap will reap // all exited processes and close their wait channels func Reap() error { now := time.Now() exits, err := sys.Reap(false) Default.Lock() for c := range Default.subscribers { for _, e := range exits { c <- runc.Exit{ Timestamp: now, Pid: e.Pid, Status: e.Status, } } } Default.Unlock() return err } // Default is the default monitor initialized for the package var Default = &Monitor{ subscribers: make(map[chan runc.Exit]struct{}), } // Monitor monitors the underlying system for process status changes type Monitor struct { sync.Mutex subscribers map[chan runc.Exit]struct{} } // Start starts the command a registers the process with the reaper func (m *Monitor) Start(c *exec.Cmd) (chan runc.Exit, error) { ec := m.Subscribe() if err := c.Start(); err != nil { m.Unsubscribe(ec) return nil, err } return ec, nil } // Wait blocks until a process is signal as dead. // User should rely on the value of the exit status to determine if the // command was successful or not. func (m *Monitor) Wait(c *exec.Cmd, ec chan runc.Exit) (int, error) { for e := range ec { if e.Pid == c.Process.Pid { // make sure we flush all IO c.Wait() m.Unsubscribe(ec) return e.Status, nil } } // return no such process if the ec channel is closed and no more exit // events will be sent return -1, ErrNoSuchProcess } // Subscribe to process exit changes func (m *Monitor) Subscribe() chan runc.Exit { c := make(chan runc.Exit, bufferSize) m.Lock() m.subscribers[c] = struct{}{} m.Unlock() return c } // Unsubscribe to process exit changes func (m *Monitor) Unsubscribe(c chan runc.Exit) { m.Lock() delete(m.subscribers, c) close(c) m.Unlock() }