closeAfterWait []io.Closer
goroutine []func() error
errch chan error // one send per goroutine
+ waitDone chan struct{}
}
// Command returns the Cmd struct to execute the named program with
if c.Process != nil {
return errors.New("exec: already started")
}
+ if c.ctx != nil {
+ select {
+ case <-c.ctx.Done():
+ c.closeDescriptors(c.closeAfterStart)
+ c.closeDescriptors(c.closeAfterWait)
+ return c.ctx.Err()
+ default:
+ }
+ }
type F func(*Cmd) (*os.File, error)
for _, setupFd := range []F{(*Cmd).stdin, (*Cmd).stdout, (*Cmd).stderr} {
}(fn)
}
+ if c.ctx != nil {
+ c.waitDone = make(chan struct{})
+ go func() {
+ select {
+ case <-c.ctx.Done():
+ c.Process.Kill()
+ case <-c.waitDone:
+ }
+ }()
+ }
+
return nil
}
}
c.finished = true
- var waitDone chan struct{}
- if c.ctx != nil {
- waitDone = make(chan struct{})
- go func() {
- select {
- case <-c.ctx.Done():
- c.Process.Kill()
- case <-waitDone:
- }
- }()
- }
state, err := c.Process.Wait()
- if waitDone != nil {
- close(waitDone)
+ if c.waitDone != nil {
+ close(c.waitDone)
}
c.ProcessState = state
t.Fatal("timeout waiting for child process death")
}
}
+
+func TestContextCancel(t *testing.T) {
+ ctx, cancel := context.WithCancel(context.Background())
+ defer cancel()
+ c := helperCommandContext(t, ctx, "cat")
+
+ r, w, err := os.Pipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ c.Stdin = r
+
+ stdout, err := c.StdoutPipe()
+ if err != nil {
+ t.Fatal(err)
+ }
+ readDone := make(chan struct{})
+ go func() {
+ defer close(readDone)
+ var a [1024]byte
+ for {
+ n, err := stdout.Read(a[:])
+ if err != nil {
+ if err != io.EOF {
+ t.Errorf("unexpected read error: %v", err)
+ }
+ return
+ }
+ t.Logf("%s", a[:n])
+ }
+ }()
+
+ if err := c.Start(); err != nil {
+ t.Fatal(err)
+ }
+
+ if err := r.Close(); err != nil {
+ t.Fatal(err)
+ }
+
+ if _, err := io.WriteString(w, "echo"); err != nil {
+ t.Fatal(err)
+ }
+
+ cancel()
+
+ // Calling cancel should have killed the process, so writes
+ // should now fail. Give the process a little while to die.
+ start := time.Now()
+ for {
+ if _, err := io.WriteString(w, "echo"); err != nil {
+ break
+ }
+ if time.Since(start) > time.Second {
+ t.Fatal("cancelling context did not stop program")
+ }
+ time.Sleep(time.Millisecond)
+ }
+
+ if err := w.Close(); err != nil {
+ t.Error("error closing write end of pipe: %v", err)
+ }
+ <-readDone
+
+ if err := c.Wait(); err == nil {
+ t.Error("program unexpectedly exited successfully")
+ } else {
+ t.Logf("exit status: %v", err)
+ }
+}