blob: 2d517d7e711ca648fc27e53bb70b428394abfc6b [file] [log] [blame]
Cosmos Nicolaou62613842014-08-25 21:57:37 -07001package modules
2
3import (
Cosmos Nicolaou62613842014-08-25 21:57:37 -07004 "flag"
Cosmos Nicolaou62613842014-08-25 21:57:37 -07005 "io"
Cosmos Nicolaou62613842014-08-25 21:57:37 -07006 "os"
7 "os/exec"
8 "strings"
9 "sync"
10 "time"
11
Cosmos Nicolaou486d3492014-09-30 22:21:20 -070012 vexec "veyron.io/veyron/veyron/lib/exec"
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -070013 "veyron.io/veyron/veyron2/vlog"
Cosmos Nicolaou62613842014-08-25 21:57:37 -070014)
15
16// execHandle implements both the command and Handle interfaces.
17type execHandle struct {
18 mu sync.Mutex
19 cmd *exec.Cmd
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -070020 name string
Cosmos Nicolaou62613842014-08-25 21:57:37 -070021 entryPoint string
22 handle *vexec.ParentHandle
Cosmos Nicolaou66afced2014-09-15 22:12:43 -070023 sh *Shell
Cosmos Nicolaou62613842014-08-25 21:57:37 -070024 stderr *os.File
Cosmos Nicolaou9ca249d2014-09-18 15:07:12 -070025 stdout io.ReadCloser
Cosmos Nicolaou62613842014-08-25 21:57:37 -070026 stdin io.WriteCloser
Bogdan Caprita490a4512014-11-20 21:12:19 -080027 procErrCh chan error
Cosmos Nicolaou62613842014-08-25 21:57:37 -070028}
29
30func testFlags() []string {
31 var fl []string
32 // pass logging flags to any subprocesses
33 for fname, fval := range vlog.Log.ExplicitlySetFlags() {
34 fl = append(fl, "--"+fname+"="+fval)
35 }
36 timeout := flag.Lookup("test.timeout")
37 if timeout == nil {
38 // not a go test binary
39 return fl
40 }
41 // must be a go test binary
42 fl = append(fl, "-test.run=TestHelperProcess")
43 val := timeout.Value.(flag.Getter).Get().(time.Duration)
44 if val.String() != timeout.DefValue {
45 // use supplied command value for subprocesses
46 fl = append(fl, "--test.timeout="+timeout.Value.String())
47 } else {
Bogdan Caprita73ca5e72014-11-24 15:55:48 -080048 // translate default value into 3m for subproccesses. The
49 // default of 10m is too long to wait in order to find out that
50 // our subprocess is wedged.
51 fl = append(fl, "--test.timeout=3m")
Cosmos Nicolaou62613842014-08-25 21:57:37 -070052 }
53 return fl
54}
55
Bogdan Caprita490a4512014-11-20 21:12:19 -080056// IsTestHelperProcess returns true if it is called in via
Cosmos Nicolaou9c9918d2014-09-23 08:45:56 -070057// -run=TestHelperProcess which normally only ever happens for subprocesses
58// run from tests.
Cosmos Nicolaou62613842014-08-25 21:57:37 -070059func IsTestHelperProcess() bool {
60 runFlag := flag.Lookup("test.run")
61 if runFlag == nil {
62 return false
63 }
64 return runFlag.Value.String() == "TestHelperProcess"
65}
66
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -070067func newExecHandle(name string) command {
Bogdan Caprita490a4512014-11-20 21:12:19 -080068 return &execHandle{name: name, entryPoint: shellEntryPoint + "=" + name, procErrCh: make(chan error, 1)}
Cosmos Nicolaou62613842014-08-25 21:57:37 -070069}
70
Cosmos Nicolaou9ca249d2014-09-18 15:07:12 -070071func (eh *execHandle) Stdout() io.Reader {
Cosmos Nicolaou62613842014-08-25 21:57:37 -070072 eh.mu.Lock()
73 defer eh.mu.Unlock()
74 return eh.stdout
75}
76
77func (eh *execHandle) Stderr() io.Reader {
78 eh.mu.Lock()
79 defer eh.mu.Unlock()
80 return eh.stderr
81}
82
Cosmos Nicolaou66afced2014-09-15 22:12:43 -070083func (eh *execHandle) Stdin() io.Writer {
Cosmos Nicolaou62613842014-08-25 21:57:37 -070084 eh.mu.Lock()
85 defer eh.mu.Unlock()
86 return eh.stdin
87}
88
Cosmos Nicolaou66afced2014-09-15 22:12:43 -070089func (eh *execHandle) CloseStdin() {
90 eh.mu.Lock()
91 eh.stdin.Close()
92 eh.mu.Unlock()
93}
94
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -070095func (eh *execHandle) envelope(sh *Shell, env []string, args ...string) ([]string, []string) {
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -070096 newargs := []string{os.Args[0]}
97 newargs = append(newargs, testFlags()...)
98 newargs = append(newargs, args...)
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -080099 // Be careful to remove any existing shellEntryPoint env vars. This
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -0700100 // can happen when subprocesses run other subprocesses etc.
101 cleaned := make([]string, 0, len(env)+1)
102 for _, e := range env {
Cosmos Nicolaoud4f00562014-11-17 20:35:48 -0800103 if strings.HasPrefix(e, shellEntryPoint+"=") {
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -0700104 continue
105 }
106 cleaned = append(cleaned, e)
107 }
108 return newargs, append(cleaned, eh.entryPoint)
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700109}
110
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700111func (eh *execHandle) start(sh *Shell, env []string, args ...string) (Handle, error) {
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700112 eh.mu.Lock()
113 defer eh.mu.Unlock()
Cosmos Nicolaou66afced2014-09-15 22:12:43 -0700114 eh.sh = sh
Cosmos Nicolaou244d3442014-10-26 14:19:42 -0700115 newargs, newenv := eh.envelope(sh, env, args[1:]...)
116 cmd := exec.Command(os.Args[0], newargs[1:]...)
117 cmd.Env = newenv
Bogdan Caprita490a4512014-11-20 21:12:19 -0800118 stderr, err := newLogfile("stderr", eh.name)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700119 if err != nil {
120 return nil, err
121 }
122 cmd.Stderr = stderr
Bogdan Caprita490a4512014-11-20 21:12:19 -0800123 // We use a custom queue-based Writer implementation for stdout to
124 // decouple the consumers of eh.stdout from the file where the child
125 // sends its output. This avoids data races between closing the file
126 // and reading from it (since cmd.Wait will wait for the all readers to
127 // be done before closing it). It also enables Shutdown to drain stdout
128 // while respecting the timeout.
129 stdout := newRW()
130 cmd.Stdout = stdout
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700131 stdin, err := cmd.StdinPipe()
132 if err != nil {
133 return nil, err
134 }
135
Jiri Simsa37893392014-11-07 10:55:45 -0800136 handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{Config: sh.config})
Cosmos Nicolaou9ca249d2014-09-18 15:07:12 -0700137 eh.stdout = stdout
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700138 eh.stderr = stderr
139 eh.stdin = stdin
140 eh.handle = handle
141 eh.cmd = cmd
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700142 vlog.VI(1).Infof("Start: %q args: %v", eh.name, cmd.Args)
143 vlog.VI(2).Infof("Start: %q env: %v", eh.name, cmd.Env)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700144 if err := handle.Start(); err != nil {
145 return nil, err
146 }
Cosmos Nicolaoue5b41502014-10-29 22:55:09 -0700147 vlog.VI(1).Infof("Started: %q, pid %d", eh.name, cmd.Process.Pid)
148 err = handle.WaitForReady(sh.startTimeout)
Bogdan Caprita490a4512014-11-20 21:12:19 -0800149 go func() {
150 eh.procErrCh <- eh.handle.Wait(0)
151 // It's now safe to close eh.stdout, since Wait only returns
152 // once all writes from the pipe to the stdout Writer have
153 // completed. Closing eh.stdout lets consumers of stdout wrap
154 // up (they'll receive EOF).
155 eh.stdout.Close()
156 }()
157
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700158 return eh, err
159}
160
Cosmos Nicolaoucc581722014-10-07 12:45:39 -0700161func (eh *execHandle) Pid() int {
162 return eh.cmd.Process.Pid
163}
164
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700165func (eh *execHandle) Shutdown(stdout, stderr io.Writer) error {
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700166 eh.mu.Lock()
167 defer eh.mu.Unlock()
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700168 vlog.VI(1).Infof("Shutdown: %q", eh.name)
Bogdan Capritae577ef02014-11-28 17:33:18 -0800169 defer vlog.VI(1).Infof("Shutdown: %q [DONE]", eh.name)
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700170 eh.stdin.Close()
Cosmos Nicolaoue664f3f2014-10-20 17:40:05 -0700171 defer eh.sh.Forget(eh)
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700172
Bogdan Caprita490a4512014-11-20 21:12:19 -0800173 waitStdout := make(chan struct{})
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700174 if stdout != nil {
Bogdan Caprita490a4512014-11-20 21:12:19 -0800175 // Drain stdout.
176 go func() {
177 io.Copy(stdout, eh.stdout)
178 close(waitStdout)
179 }()
180 } else {
181 close(waitStdout)
Cosmos Nicolaou2cb05fb2014-10-23 13:37:32 -0700182 }
183
Bogdan Caprita490a4512014-11-20 21:12:19 -0800184 var procErr error
185 select {
186 case procErr = <-eh.procErrCh:
187 // The child has exited already.
188 case <-time.After(eh.sh.waitTimeout):
189 // Time out waiting for child to exit.
190 procErr = vexec.ErrTimeout
191 // Force close stdout to unblock any readers of stdout
192 // (including the drain loop started above).
193 eh.stdout.Close()
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700194 }
Bogdan Caprita490a4512014-11-20 21:12:19 -0800195 <-waitStdout
196
197 // Transcribe stderr.
198 outputFromFile(eh.stderr, stderr)
Robin Thellend1c8a8282014-12-09 10:38:36 -0800199 os.Remove(eh.stderr.Name())
Bogdan Caprita490a4512014-11-20 21:12:19 -0800200
Cosmos Nicolaoubbae3882014-10-02 22:58:19 -0700201 return procErr
Cosmos Nicolaou62613842014-08-25 21:57:37 -0700202}