github.com/m3db/m3@v1.5.0/src/m3em/os/exec/process_monitor.go (about) 1 // Copyright (c) 2017 Uber Technologies, Inc. 2 // 3 // Permission is hereby granted, free of charge, to any person obtaining a copy 4 // of this software and associated documentation files (the "Software"), to deal 5 // in the Software without restriction, including without limitation the rights 6 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 // copies of the Software, and to permit persons to whom the Software is 8 // furnished to do so, subject to the following conditions: 9 // 10 // The above copyright notice and this permission notice shall be included in 11 // all copies or substantial portions of the Software. 12 // 13 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 // THE SOFTWARE. 20 21 package exec 22 23 import ( 24 "context" 25 "fmt" 26 "os" 27 oexec "os/exec" 28 "path" 29 "path/filepath" 30 "sync" 31 32 xerrors "github.com/m3db/m3/src/x/errors" 33 ) 34 35 var ( 36 defaultStdoutSuffix = "out" 37 defaultStderrSuffix = "err" 38 ) 39 40 var ( 41 errUnableToStartClosed = fmt.Errorf("unable to start: process monitor Closed()") 42 errUnableToStartRunning = fmt.Errorf("unable to start: process already running") 43 errUnableToStopStoped = fmt.Errorf("unable to stop: process not running") 44 ) 45 46 func (m EnvMap) toSlice() []string { 47 if m == nil || len(m) == 0 { 48 return nil 49 } 50 envVars := os.Environ() 51 for k, v := range m { 52 envVars = append(envVars, fmt.Sprintf("%s=%s", k, v)) 53 } 54 return envVars 55 } 56 57 type processListener struct { 58 completeFn func() 59 errFn func(error) 60 } 61 62 // NewProcessListener returns a new ProcessListener 63 func NewProcessListener( 64 completeFn func(), 65 errFn func(error), 66 ) ProcessListener { 67 return &processListener{ 68 completeFn: completeFn, 69 errFn: errFn, 70 } 71 } 72 73 func (pl *processListener) OnComplete() { 74 if fn := pl.completeFn; fn != nil { 75 fn() 76 } 77 } 78 79 func (pl *processListener) OnError(err error) { 80 if fn := pl.errFn; fn != nil { 81 fn(err) 82 } 83 } 84 85 type startFn func() 86 87 type processMonitor struct { 88 sync.Mutex 89 cmd *oexec.Cmd 90 cancelFunc context.CancelFunc 91 stdoutPath string 92 stderrPath string 93 stdoutFd *os.File 94 stderrFd *os.File 95 startFn startFn 96 listener ProcessListener 97 err error 98 running bool 99 done bool 100 } 101 102 // NewProcessMonitor creates a new ProcessMonitor 103 func NewProcessMonitor(cmd Cmd, pl ProcessListener) (ProcessMonitor, error) { 104 if err := cmd.Validate(); err != nil { 105 return nil, err 106 } 107 108 fileInfo, err := os.Stat(cmd.OutputDir) 109 if err != nil { 110 return nil, fmt.Errorf("specified directory does not exist: %v", err) 111 } 112 113 if !fileInfo.IsDir() { 114 return nil, fmt.Errorf("specified path is not a directory: %v", cmd.OutputDir) 115 } 116 117 base := filepath.Base(cmd.Path) 118 stdoutPath := outputPath(cmd.OutputDir, base, defaultStdoutSuffix) 119 stdoutWriter, err := newWriter(stdoutPath) 120 if err != nil { 121 return nil, fmt.Errorf("unable to open stdout writer: %v", err) 122 } 123 124 stderrPath := outputPath(cmd.OutputDir, base, defaultStderrSuffix) 125 stderrWriter, err := newWriter(stderrPath) 126 if err != nil { 127 stdoutWriter.Close() 128 return nil, fmt.Errorf("unable to open stderr writer: %v", err) 129 } 130 131 ctx, cancel := context.WithCancel(context.Background()) 132 ocmd := oexec.CommandContext(ctx, cmd.Path) 133 ocmd.Args = cmd.Args 134 ocmd.Dir = cmd.OutputDir 135 ocmd.Stderr = stderrWriter 136 ocmd.Stdout = stdoutWriter 137 ocmd.Env = cmd.Env.toSlice() 138 139 pm := &processMonitor{ 140 listener: pl, 141 stdoutFd: stdoutWriter, 142 stderrFd: stderrWriter, 143 stdoutPath: stdoutPath, 144 stderrPath: stderrPath, 145 cancelFunc: cancel, 146 cmd: ocmd, 147 } 148 pm.startFn = pm.startAsync 149 150 return pm, nil 151 } 152 153 func (pm *processMonitor) notifyListener(err error) { 154 if pm.listener == nil { 155 return 156 } 157 if err == nil { 158 pm.listener.OnComplete() 159 } else { 160 pm.listener.OnError(err) 161 } 162 } 163 164 func (pm *processMonitor) Start() error { 165 pm.Lock() 166 if pm.done { 167 pm.Unlock() 168 return errUnableToStartClosed 169 } 170 171 if pm.err != nil { 172 pm.Unlock() 173 return pm.err 174 } 175 176 if pm.running { 177 pm.Unlock() 178 return errUnableToStartRunning 179 } 180 pm.Unlock() 181 182 pm.startFn() 183 return nil 184 } 185 186 func (pm *processMonitor) startAsync() { 187 go pm.startSync() 188 } 189 190 func (pm *processMonitor) startSync() { 191 pm.Lock() 192 pm.running = true 193 pm.Unlock() 194 195 err := pm.cmd.Run() 196 pm.Lock() 197 defer pm.Unlock() 198 199 // only notify when Stop() has not been called explicitly 200 if done := pm.done; !done { 201 pm.notifyListener(err) 202 pm.err = err 203 } 204 pm.running = false 205 } 206 207 func (pm *processMonitor) Stop() error { 208 pm.Lock() 209 defer pm.Unlock() 210 pm.done = true 211 return pm.stopWithLock() 212 } 213 214 func (pm *processMonitor) stopWithLock() error { 215 if pm.err != nil { 216 return pm.err 217 } 218 219 if !pm.running { 220 return errUnableToStopStoped 221 } 222 223 pm.cancelFunc() 224 return nil 225 } 226 227 func (pm *processMonitor) Running() bool { 228 pm.Lock() 229 defer pm.Unlock() 230 return pm.running 231 } 232 233 func (pm *processMonitor) Err() error { 234 pm.Lock() 235 defer pm.Unlock() 236 return pm.err 237 } 238 239 func (pm *processMonitor) Close() error { 240 pm.Lock() 241 defer pm.Unlock() 242 if pm.done { 243 return nil 244 } 245 246 var multiErr xerrors.MultiError 247 if pm.running { 248 multiErr = multiErr.Add(pm.stopWithLock()) 249 } 250 251 if pm.stderrFd != nil { 252 multiErr = multiErr.Add(pm.stderrFd.Close()) 253 pm.stderrFd = nil 254 } 255 if pm.stdoutFd != nil { 256 multiErr = multiErr.Add(pm.stdoutFd.Close()) 257 pm.stdoutFd = nil 258 } 259 pm.done = true 260 return multiErr.FinalError() 261 } 262 263 func (pm *processMonitor) StdoutPath() string { 264 return pm.stdoutPath 265 } 266 267 func (pm *processMonitor) StderrPath() string { 268 return pm.stderrPath 269 } 270 271 func outputPath(outputDir string, prefix string, suffix string) string { 272 return path.Join(outputDir, fmt.Sprintf("%s.%s", prefix, suffix)) 273 } 274 275 func newWriter(outputPath string) (*os.File, error) { 276 fd, err := os.OpenFile(outputPath, os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0755) 277 if err != nil { 278 return nil, err 279 } 280 return fd, nil 281 }