@@ -167,7 +167,10 @@ type Options struct {
167167
168168 // BeforeExec is a list of functions called immediately before starting
169169 // 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.
171174 BeforeExec []func (cmd * exec.Cmd )
172175
173176 // LineBufferSize sets the size of the OutputStream line buffer. The default
@@ -285,24 +288,41 @@ func (c *Cmd) StartWithStdin(in io.Reader) <-chan Status {
285288 if c .statusChan != nil {
286289 return c .statusChan
287290 }
288-
289291 c .statusChan = make (chan Status , 1 )
292+
290293 go c .run (in )
291294 return c .statusChan
292295}
293296
294297// Stop stops the command by sending its process group a SIGTERM signal.
295298// Stop is idempotent. Stopping and already stopped command returns nil.
296299//
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.
302313func (c * Cmd ) Stop () error {
303314 c .Lock ()
304315 defer c .Unlock ()
305316
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+
306326 // c.statusChan is created in StartWithStdin()/Start(), so if nil the caller
307327 // hasn't started the command yet. c.started is set true in run() only after
308328 // the underlying os/exec.Cmd.Start() has returned without an error, so we're
@@ -319,10 +339,6 @@ func (c *Cmd) Stop() error {
319339 return nil
320340 }
321341
322- // Flag that command was stopped, it didn't complete. This results in
323- // status.Complete = false
324- c .stopped = true
325-
326342 // Signal the process group (-pid), not just the process, so that the process
327343 // and all its children are signaled. Else, child procs can keep running and
328344 // keep the stdout/stderr fd open and cause cmd.Wait to hang.
@@ -412,7 +428,6 @@ func (c *Cmd) run(in io.Reader) {
412428 // Set exec.Cmd.Stdout and .Stderr to our concurrent-safe stdout/stderr
413429 // buffer, stream both, or neither
414430 switch {
415-
416431 case c .stdoutBuf != nil && c .stderrBuf != nil && c .stdoutStream != nil : // buffer and stream
417432 cmd .Stdout = io .MultiWriter (c .stdoutStream , c .stdoutBuf )
418433 cmd .Stderr = io .MultiWriter (c .stderrStream , c .stderrBuf )
@@ -459,6 +474,15 @@ func (c *Cmd) run(in io.Reader) {
459474 // Run all optional commands to customize underlying os/exe.Cmd.
460475 for _ , f := range c .beforeExecFuncs {
461476 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+ }
462486 }
463487
464488 // //////////////////////////////////////////////////////////////////////
0 commit comments