Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 1 | package modules |
| 2 | |
| 3 | import ( |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 4 | "flag" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 5 | "io" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 6 | "os" |
| 7 | "os/exec" |
| 8 | "strings" |
| 9 | "sync" |
| 10 | "time" |
| 11 | |
Cosmos Nicolaou | 486d349 | 2014-09-30 22:21:20 -0700 | [diff] [blame] | 12 | vexec "veyron.io/veyron/veyron/lib/exec" |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 13 | "veyron.io/veyron/veyron2/vlog" |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 14 | ) |
| 15 | |
| 16 | // execHandle implements both the command and Handle interfaces. |
| 17 | type execHandle struct { |
| 18 | mu sync.Mutex |
| 19 | cmd *exec.Cmd |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 20 | name string |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 21 | entryPoint string |
| 22 | handle *vexec.ParentHandle |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 23 | sh *Shell |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 24 | stderr *os.File |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 25 | stdout io.ReadCloser |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 26 | stdin io.WriteCloser |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 27 | procErrCh chan error |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 28 | } |
| 29 | |
| 30 | func 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 Caprita | 73ca5e7 | 2014-11-24 15:55:48 -0800 | [diff] [blame] | 48 | // 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 Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 52 | } |
| 53 | return fl |
| 54 | } |
| 55 | |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 56 | // IsTestHelperProcess returns true if it is called in via |
Cosmos Nicolaou | 9c9918d | 2014-09-23 08:45:56 -0700 | [diff] [blame] | 57 | // -run=TestHelperProcess which normally only ever happens for subprocesses |
| 58 | // run from tests. |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 59 | func 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 Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 67 | func newExecHandle(name string) command { |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 68 | return &execHandle{name: name, entryPoint: shellEntryPoint + "=" + name, procErrCh: make(chan error, 1)} |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 69 | } |
| 70 | |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 71 | func (eh *execHandle) Stdout() io.Reader { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 72 | eh.mu.Lock() |
| 73 | defer eh.mu.Unlock() |
| 74 | return eh.stdout |
| 75 | } |
| 76 | |
| 77 | func (eh *execHandle) Stderr() io.Reader { |
| 78 | eh.mu.Lock() |
| 79 | defer eh.mu.Unlock() |
| 80 | return eh.stderr |
| 81 | } |
| 82 | |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 83 | func (eh *execHandle) Stdin() io.Writer { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 84 | eh.mu.Lock() |
| 85 | defer eh.mu.Unlock() |
| 86 | return eh.stdin |
| 87 | } |
| 88 | |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 89 | func (eh *execHandle) CloseStdin() { |
| 90 | eh.mu.Lock() |
| 91 | eh.stdin.Close() |
| 92 | eh.mu.Unlock() |
| 93 | } |
| 94 | |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 95 | func (eh *execHandle) envelope(sh *Shell, env []string, args ...string) ([]string, []string) { |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 96 | newargs := []string{os.Args[0]} |
| 97 | newargs = append(newargs, testFlags()...) |
| 98 | newargs = append(newargs, args...) |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 99 | // Be careful to remove any existing shellEntryPoint env vars. This |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 100 | // can happen when subprocesses run other subprocesses etc. |
| 101 | cleaned := make([]string, 0, len(env)+1) |
| 102 | for _, e := range env { |
Cosmos Nicolaou | d4f0056 | 2014-11-17 20:35:48 -0800 | [diff] [blame] | 103 | if strings.HasPrefix(e, shellEntryPoint+"=") { |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 104 | continue |
| 105 | } |
| 106 | cleaned = append(cleaned, e) |
| 107 | } |
| 108 | return newargs, append(cleaned, eh.entryPoint) |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 109 | } |
| 110 | |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 111 | func (eh *execHandle) start(sh *Shell, env []string, args ...string) (Handle, error) { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 112 | eh.mu.Lock() |
| 113 | defer eh.mu.Unlock() |
Cosmos Nicolaou | 66afced | 2014-09-15 22:12:43 -0700 | [diff] [blame] | 114 | eh.sh = sh |
Cosmos Nicolaou | 244d344 | 2014-10-26 14:19:42 -0700 | [diff] [blame] | 115 | newargs, newenv := eh.envelope(sh, env, args[1:]...) |
| 116 | cmd := exec.Command(os.Args[0], newargs[1:]...) |
| 117 | cmd.Env = newenv |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 118 | stderr, err := newLogfile("stderr", eh.name) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 119 | if err != nil { |
| 120 | return nil, err |
| 121 | } |
| 122 | cmd.Stderr = stderr |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 123 | // 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 Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 131 | stdin, err := cmd.StdinPipe() |
| 132 | if err != nil { |
| 133 | return nil, err |
| 134 | } |
| 135 | |
Jiri Simsa | 3789339 | 2014-11-07 10:55:45 -0800 | [diff] [blame] | 136 | handle := vexec.NewParentHandle(cmd, vexec.ConfigOpt{Config: sh.config}) |
Cosmos Nicolaou | 9ca249d | 2014-09-18 15:07:12 -0700 | [diff] [blame] | 137 | eh.stdout = stdout |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 138 | eh.stderr = stderr |
| 139 | eh.stdin = stdin |
| 140 | eh.handle = handle |
| 141 | eh.cmd = cmd |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 142 | 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 Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 144 | if err := handle.Start(); err != nil { |
| 145 | return nil, err |
| 146 | } |
Cosmos Nicolaou | e5b4150 | 2014-10-29 22:55:09 -0700 | [diff] [blame] | 147 | vlog.VI(1).Infof("Started: %q, pid %d", eh.name, cmd.Process.Pid) |
| 148 | err = handle.WaitForReady(sh.startTimeout) |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 149 | 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 Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 158 | return eh, err |
| 159 | } |
| 160 | |
Cosmos Nicolaou | cc58172 | 2014-10-07 12:45:39 -0700 | [diff] [blame] | 161 | func (eh *execHandle) Pid() int { |
| 162 | return eh.cmd.Process.Pid |
| 163 | } |
| 164 | |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 165 | func (eh *execHandle) Shutdown(stdout, stderr io.Writer) error { |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 166 | eh.mu.Lock() |
| 167 | defer eh.mu.Unlock() |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 168 | vlog.VI(1).Infof("Shutdown: %q", eh.name) |
Bogdan Caprita | e577ef0 | 2014-11-28 17:33:18 -0800 | [diff] [blame] | 169 | defer vlog.VI(1).Infof("Shutdown: %q [DONE]", eh.name) |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 170 | eh.stdin.Close() |
Cosmos Nicolaou | e664f3f | 2014-10-20 17:40:05 -0700 | [diff] [blame] | 171 | defer eh.sh.Forget(eh) |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 172 | |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 173 | waitStdout := make(chan struct{}) |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 174 | if stdout != nil { |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 175 | // Drain stdout. |
| 176 | go func() { |
| 177 | io.Copy(stdout, eh.stdout) |
| 178 | close(waitStdout) |
| 179 | }() |
| 180 | } else { |
| 181 | close(waitStdout) |
Cosmos Nicolaou | 2cb05fb | 2014-10-23 13:37:32 -0700 | [diff] [blame] | 182 | } |
| 183 | |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 184 | 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 Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 194 | } |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 195 | <-waitStdout |
| 196 | |
| 197 | // Transcribe stderr. |
| 198 | outputFromFile(eh.stderr, stderr) |
Robin Thellend | 1c8a828 | 2014-12-09 10:38:36 -0800 | [diff] [blame] | 199 | os.Remove(eh.stderr.Name()) |
Bogdan Caprita | 490a451 | 2014-11-20 21:12:19 -0800 | [diff] [blame] | 200 | |
Cosmos Nicolaou | bbae388 | 2014-10-02 22:58:19 -0700 | [diff] [blame] | 201 | return procErr |
Cosmos Nicolaou | 6261384 | 2014-08-25 21:57:37 -0700 | [diff] [blame] | 202 | } |