github.com/demonoid81/containerd@v1.3.4/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 "github.com/containerd/containerd/sys" 27 runc "github.com/containerd/go-runc" 28 "github.com/pkg/errors" 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 := sys.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 }