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 }