github.com/containerd/Containerd@v1.4.13/sys/reaper/reaper_unix.go (about) 1 // +build !windows 2 3 /* 4 Copyright The containerd Authors. 5 6 Licensed under the Apache License, Version 2.0 (the "License"); 7 you may not use this file except in compliance with the License. 8 You may obtain a copy of the License at 9 10 http://www.apache.org/licenses/LICENSE-2.0 11 12 Unless required by applicable law or agreed to in writing, software 13 distributed under the License is distributed on an "AS IS" BASIS, 14 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 See the License for the specific language governing permissions and 16 limitations under the License. 17 */ 18 19 package reaper 20 21 import ( 22 "os/exec" 23 "sync" 24 "time" 25 26 runc "github.com/containerd/go-runc" 27 "github.com/pkg/errors" 28 "golang.org/x/sys/unix" 29 ) 30 31 // ErrNoSuchProcess is returned when the process no longer exists 32 var ErrNoSuchProcess = errors.New("no such process") 33 34 const bufferSize = 32 35 36 type subscriber struct { 37 sync.Mutex 38 c chan runc.Exit 39 closed bool 40 } 41 42 func (s *subscriber) close() { 43 s.Lock() 44 if s.closed { 45 s.Unlock() 46 return 47 } 48 close(s.c) 49 s.closed = true 50 s.Unlock() 51 } 52 53 func (s *subscriber) do(fn func()) { 54 s.Lock() 55 fn() 56 s.Unlock() 57 } 58 59 // Reap should be called when the process receives an SIGCHLD. Reap will reap 60 // all exited processes and close their wait channels 61 func Reap() error { 62 now := time.Now() 63 exits, err := reap(false) 64 for _, e := range exits { 65 done := Default.notify(runc.Exit{ 66 Timestamp: now, 67 Pid: e.Pid, 68 Status: e.Status, 69 }) 70 71 select { 72 case <-done: 73 case <-time.After(1 * time.Second): 74 } 75 } 76 return err 77 } 78 79 // Default is the default monitor initialized for the package 80 var Default = &Monitor{ 81 subscribers: make(map[chan runc.Exit]*subscriber), 82 } 83 84 // Monitor monitors the underlying system for process status changes 85 type Monitor struct { 86 sync.Mutex 87 88 subscribers map[chan runc.Exit]*subscriber 89 } 90 91 // Start starts the command a registers the process with the reaper 92 func (m *Monitor) Start(c *exec.Cmd) (chan runc.Exit, error) { 93 ec := m.Subscribe() 94 if err := c.Start(); err != nil { 95 m.Unsubscribe(ec) 96 return nil, err 97 } 98 return ec, nil 99 } 100 101 // Wait blocks until a process is signal as dead. 102 // User should rely on the value of the exit status to determine if the 103 // command was successful or not. 104 func (m *Monitor) Wait(c *exec.Cmd, ec chan runc.Exit) (int, error) { 105 for e := range ec { 106 if e.Pid == c.Process.Pid { 107 // make sure we flush all IO 108 c.Wait() 109 m.Unsubscribe(ec) 110 return e.Status, nil 111 } 112 } 113 // return no such process if the ec channel is closed and no more exit 114 // events will be sent 115 return -1, ErrNoSuchProcess 116 } 117 118 // Subscribe to process exit changes 119 func (m *Monitor) Subscribe() chan runc.Exit { 120 c := make(chan runc.Exit, bufferSize) 121 m.Lock() 122 m.subscribers[c] = &subscriber{ 123 c: c, 124 } 125 m.Unlock() 126 return c 127 } 128 129 // Unsubscribe to process exit changes 130 func (m *Monitor) Unsubscribe(c chan runc.Exit) { 131 m.Lock() 132 s, ok := m.subscribers[c] 133 if !ok { 134 m.Unlock() 135 return 136 } 137 s.close() 138 delete(m.subscribers, c) 139 m.Unlock() 140 } 141 142 func (m *Monitor) getSubscribers() map[chan runc.Exit]*subscriber { 143 out := make(map[chan runc.Exit]*subscriber) 144 m.Lock() 145 for k, v := range m.subscribers { 146 out[k] = v 147 } 148 m.Unlock() 149 return out 150 } 151 152 func (m *Monitor) notify(e runc.Exit) chan struct{} { 153 const timeout = 1 * time.Millisecond 154 var ( 155 done = make(chan struct{}, 1) 156 timer = time.NewTimer(timeout) 157 success = make(map[chan runc.Exit]struct{}) 158 ) 159 stop(timer, true) 160 161 go func() { 162 defer close(done) 163 164 for { 165 var ( 166 failed int 167 subscribers = m.getSubscribers() 168 ) 169 for _, s := range subscribers { 170 s.do(func() { 171 if s.closed { 172 return 173 } 174 if _, ok := success[s.c]; ok { 175 return 176 } 177 timer.Reset(timeout) 178 recv := true 179 select { 180 case s.c <- e: 181 success[s.c] = struct{}{} 182 case <-timer.C: 183 recv = false 184 failed++ 185 } 186 stop(timer, recv) 187 }) 188 } 189 // all subscribers received the message 190 if failed == 0 { 191 return 192 } 193 } 194 }() 195 return done 196 } 197 198 func stop(timer *time.Timer, recv bool) { 199 if !timer.Stop() && recv { 200 <-timer.C 201 } 202 } 203 204 // exit is the wait4 information from an exited process 205 type exit struct { 206 Pid int 207 Status int 208 } 209 210 // reap reaps all child processes for the calling process and returns their 211 // exit information 212 func reap(wait bool) (exits []exit, err error) { 213 var ( 214 ws unix.WaitStatus 215 rus unix.Rusage 216 ) 217 flag := unix.WNOHANG 218 if wait { 219 flag = 0 220 } 221 for { 222 pid, err := unix.Wait4(-1, &ws, flag, &rus) 223 if err != nil { 224 if err == unix.ECHILD { 225 return exits, nil 226 } 227 return exits, err 228 } 229 if pid <= 0 { 230 return exits, nil 231 } 232 exits = append(exits, exit{ 233 Pid: pid, 234 Status: exitStatus(ws), 235 }) 236 } 237 } 238 239 const exitSignalOffset = 128 240 241 // exitStatus returns the correct exit status for a process based on if it 242 // was signaled or exited cleanly 243 func exitStatus(status unix.WaitStatus) int { 244 if status.Signaled() { 245 return exitSignalOffset + int(status.Signal()) 246 } 247 return status.ExitStatus() 248 }