@@ -167,7 +167,10 @@ type Options struct {
167
167
168
168
// BeforeExec is a list of functions called immediately before starting
169
169
// the real command. These functions can be used to customize the underlying
170
- // os/exec.Cmd. For example, to set SysProcAttr.
170
+ // os/exec.Cmd. For example, to set SysProcAttr. If Stop is called while
171
+ // executing these functions, Start (or StartWithStdin) returns after the
172
+ // currently executing function returns. Stop does not stop these functions,
173
+ // but a future version will pass a context for cancellation.
171
174
BeforeExec []func (cmd * exec.Cmd )
172
175
173
176
// LineBufferSize sets the size of the OutputStream line buffer. The default
@@ -285,24 +288,41 @@ func (c *Cmd) StartWithStdin(in io.Reader) <-chan Status {
285
288
if c .statusChan != nil {
286
289
return c .statusChan
287
290
}
288
-
289
291
c .statusChan = make (chan Status , 1 )
292
+
290
293
go c .run (in )
291
294
return c .statusChan
292
295
}
293
296
294
297
// Stop stops the command by sending its process group a SIGTERM signal.
295
298
// Stop is idempotent. Stopping and already stopped command returns nil.
296
299
//
297
- // Stop returns ErrNotStarted if called before Start or StartWithStdin. If the
298
- // command is very slow to start, Stop can return ErrNotStarted after calling
299
- // Start or StartWithStdin because this package is still waiting for the system
300
- // to start the process. All other return errors are from the low-level system
301
- // function for process termination.
300
+ // Stop returns ErrNotStarted if:
301
+ // 1. Start (or StartWithStdin) was not called yet, or
302
+ // 2. Start was called but Stop was called before starting the command, or
303
+ // 3. Start was called but the system is still starting the command
304
+ //
305
+ // The second case can happen if Stop is called while executing Options.BeforeExec
306
+ // functions. In this case, Status.Exit = -1 and other Status fields are zero values.
307
+ // The third case is a race condition that might be fixed in future versions of
308
+ // this package. It means you should not rely on Stop immediately after calling Start;
309
+ // instead, call Start and wait for Status.PID to be set, which signals that the system
310
+ // has completely started the command.
311
+ //
312
+ // All other return errors are from the low-level system function for process termination.
302
313
func (c * Cmd ) Stop () error {
303
314
c .Lock ()
304
315
defer c .Unlock ()
305
316
317
+ // Flag that command was stopped, it didn't complete. This results in
318
+ // status.Complete = false. If beforeExecFuncs are executing (Cmd hasn't
319
+ // started yet), run will return after the current func returns (fixes
320
+ // bug https://github.com/go-cmd/cmd/issues/94).
321
+ if c .stopped {
322
+ return nil
323
+ }
324
+ c .stopped = true
325
+
306
326
// c.statusChan is created in StartWithStdin()/Start(), so if nil the caller
307
327
// hasn't started the command yet. c.started is set true in run() only after
308
328
// the underlying os/exec.Cmd.Start() has returned without an error, so we're
@@ -319,10 +339,6 @@ func (c *Cmd) Stop() error {
319
339
return nil
320
340
}
321
341
322
- // Flag that command was stopped, it didn't complete. This results in
323
- // status.Complete = false
324
- c .stopped = true
325
-
326
342
// Signal the process group (-pid), not just the process, so that the process
327
343
// and all its children are signaled. Else, child procs can keep running and
328
344
// keep the stdout/stderr fd open and cause cmd.Wait to hang.
@@ -412,7 +428,6 @@ func (c *Cmd) run(in io.Reader) {
412
428
// Set exec.Cmd.Stdout and .Stderr to our concurrent-safe stdout/stderr
413
429
// buffer, stream both, or neither
414
430
switch {
415
-
416
431
case c .stdoutBuf != nil && c .stderrBuf != nil && c .stdoutStream != nil : // buffer and stream
417
432
cmd .Stdout = io .MultiWriter (c .stdoutStream , c .stdoutBuf )
418
433
cmd .Stderr = io .MultiWriter (c .stderrStream , c .stderrBuf )
@@ -459,6 +474,15 @@ func (c *Cmd) run(in io.Reader) {
459
474
// Run all optional commands to customize underlying os/exe.Cmd.
460
475
for _ , f := range c .beforeExecFuncs {
461
476
f (cmd )
477
+
478
+ // Return early if Stop called
479
+ // https://github.com/go-cmd/cmd/issues/94
480
+ c .Lock ()
481
+ stopped := c .stopped
482
+ c .Unlock ()
483
+ if stopped {
484
+ return
485
+ }
462
486
}
463
487
464
488
// //////////////////////////////////////////////////////////////////////
0 commit comments