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  }