github.com/mgoltzsche/ctnr@v0.7.1-alpha/run/runcrunner/runcprocess.go (about)

     1  package runcrunner
     2  
     3  import (
     4  	"os"
     5  	"os/exec"
     6  	"strings"
     7  	"sync"
     8  	"syscall"
     9  	"time"
    10  
    11  	exterrors "github.com/mgoltzsche/ctnr/pkg/errors"
    12  	"github.com/mgoltzsche/ctnr/pkg/log"
    13  	"github.com/mgoltzsche/ctnr/run"
    14  	"github.com/pkg/errors"
    15  )
    16  
    17  type RuncProcess struct {
    18  	args     []string
    19  	io       run.ContainerIO
    20  	terminal bool
    21  	cmd      *exec.Cmd
    22  	mutex    *sync.Mutex
    23  	wait     *sync.WaitGroup
    24  	debug    log.Logger
    25  	err      error
    26  }
    27  
    28  func NewRuncProcess(args []string, terminal bool, io run.ContainerIO, debug log.Logger) (c *RuncProcess) {
    29  	return &RuncProcess{
    30  		args:     args,
    31  		io:       io,
    32  		terminal: terminal,
    33  		mutex:    &sync.Mutex{},
    34  		wait:     &sync.WaitGroup{},
    35  		debug:    debug,
    36  	}
    37  }
    38  
    39  func (c *RuncProcess) Start() (err error) {
    40  	c.debug.Printf("Starting process %p: %s", c, strings.Join(c.args, " "))
    41  	c.mutex.Lock()
    42  	defer c.mutex.Unlock()
    43  	if c.cmd != nil {
    44  		return errors.Errorf("start: process already started (%+v)", c.args)
    45  	}
    46  
    47  	c.err = nil
    48  	c.cmd = exec.Command(c.args[0], c.args[1:]...)
    49  	c.cmd.Stdout = c.io.Stdout
    50  	c.cmd.Stderr = c.io.Stderr
    51  	c.cmd.Stdin = c.io.Stdin
    52  
    53  	if c.terminal && c.cmd.Stdin == nil {
    54  		c.cmd.Stdin = os.Stdin
    55  	}
    56  
    57  	if !c.terminal {
    58  		// Run in separate process group to be able to control orderly shutdown
    59  		c.cmd.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}
    60  	}
    61  
    62  	if err = c.cmd.Start(); err != nil {
    63  		return errors.Wrapf(err, "exec %+v", c.args)
    64  	}
    65  
    66  	c.wait.Add(1)
    67  	go c.cmdWait()
    68  	return
    69  }
    70  
    71  func (c *RuncProcess) cmdWait() {
    72  	defer c.wait.Done()
    73  	argStr := strings.Join(c.args, " ")
    74  	c.err = run.NewExitError(c.cmd.Wait(), argStr)
    75  	c.debug.Printf("Process %p terminated", c)
    76  }
    77  
    78  func (c *RuncProcess) Stop() {
    79  	if c.cmd == nil {
    80  		return
    81  	}
    82  
    83  	go c.stop()
    84  }
    85  
    86  func (c *RuncProcess) stop() {
    87  	c.mutex.Lock()
    88  	defer c.mutex.Unlock()
    89  
    90  	if c.cmd == nil {
    91  		return
    92  	}
    93  
    94  	if c.cmd.Process != nil && c.cmd.ProcessState != nil && !c.cmd.ProcessState.Exited() {
    95  		// Terminate process orderly
    96  		c.debug.Println("Terminating process...")
    97  		c.cmd.Process.Signal(syscall.SIGINT)
    98  	}
    99  
   100  	quit := make(chan error, 1)
   101  	go func() {
   102  		quit <- c.Wait()
   103  	}()
   104  	var err, ex error
   105  	select {
   106  	case <-time.After(time.Duration(10000000)): // TODO: read value from OCI runtime configuration
   107  		// Kill process after timeout
   108  		if c.cmd.Process != nil {
   109  			c.debug.Println("Killing process since stop timeout exceeded")
   110  			e := c.cmd.Process.Kill()
   111  			if e != nil && c.cmd.ProcessState != nil && !c.cmd.ProcessState.Exited() {
   112  				err = errors.Wrapf(e, "stop: failed to kill pid %d (%+v)", c.cmd.ProcessState.Pid(), c.args)
   113  			}
   114  			c.Wait()
   115  		}
   116  		ex = <-quit
   117  	case ex = <-quit:
   118  	}
   119  	close(quit)
   120  	c.cmd = nil
   121  	c.err = exterrors.Append(ex, err)
   122  	return
   123  }
   124  
   125  func (c *RuncProcess) Wait() error {
   126  	c.wait.Wait()
   127  	return c.err
   128  }
   129  
   130  func (c *RuncProcess) Close() (err error) {
   131  	c.Stop()
   132  	return c.Wait()
   133  }