invoke.go (10526B)
1 // Copyright 2020 The Go Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style 3 // license that can be found in the LICENSE file. 4 5 // Package gocommand is a helper for calling the go command. 6 package gocommand 7 8 import ( 9 "bytes" 10 "context" 11 "fmt" 12 "io" 13 "log" 14 "os" 15 "regexp" 16 "runtime" 17 "strconv" 18 "strings" 19 "sync" 20 "time" 21 22 exec "golang.org/x/sys/execabs" 23 24 "golang.org/x/tools/internal/event" 25 ) 26 27 // An Runner will run go command invocations and serialize 28 // them if it sees a concurrency error. 29 type Runner struct { 30 // once guards the runner initialization. 31 once sync.Once 32 33 // inFlight tracks available workers. 34 inFlight chan struct{} 35 36 // serialized guards the ability to run a go command serially, 37 // to avoid deadlocks when claiming workers. 38 serialized chan struct{} 39 } 40 41 const maxInFlight = 10 42 43 func (runner *Runner) initialize() { 44 runner.once.Do(func() { 45 runner.inFlight = make(chan struct{}, maxInFlight) 46 runner.serialized = make(chan struct{}, 1) 47 }) 48 } 49 50 // 1.13: go: updates to go.mod needed, but contents have changed 51 // 1.14: go: updating go.mod: existing contents have changed since last read 52 var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`) 53 54 // Run is a convenience wrapper around RunRaw. 55 // It returns only stdout and a "friendly" error. 56 func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) { 57 stdout, _, friendly, _ := runner.RunRaw(ctx, inv) 58 return stdout, friendly 59 } 60 61 // RunPiped runs the invocation serially, always waiting for any concurrent 62 // invocations to complete first. 63 func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error { 64 _, err := runner.runPiped(ctx, inv, stdout, stderr) 65 return err 66 } 67 68 // RunRaw runs the invocation, serializing requests only if they fight over 69 // go.mod changes. 70 func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { 71 // Make sure the runner is always initialized. 72 runner.initialize() 73 74 // First, try to run the go command concurrently. 75 stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv) 76 77 // If we encounter a load concurrency error, we need to retry serially. 78 if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) { 79 return stdout, stderr, friendlyErr, err 80 } 81 event.Error(ctx, "Load concurrency error, will retry serially", err) 82 83 // Run serially by calling runPiped. 84 stdout.Reset() 85 stderr.Reset() 86 friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr) 87 return stdout, stderr, friendlyErr, err 88 } 89 90 func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { 91 // Wait for 1 worker to become available. 92 select { 93 case <-ctx.Done(): 94 return nil, nil, nil, ctx.Err() 95 case runner.inFlight <- struct{}{}: 96 defer func() { <-runner.inFlight }() 97 } 98 99 stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} 100 friendlyErr, err := inv.runWithFriendlyError(ctx, stdout, stderr) 101 return stdout, stderr, friendlyErr, err 102 } 103 104 func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) { 105 // Make sure the runner is always initialized. 106 runner.initialize() 107 108 // Acquire the serialization lock. This avoids deadlocks between two 109 // runPiped commands. 110 select { 111 case <-ctx.Done(): 112 return nil, ctx.Err() 113 case runner.serialized <- struct{}{}: 114 defer func() { <-runner.serialized }() 115 } 116 117 // Wait for all in-progress go commands to return before proceeding, 118 // to avoid load concurrency errors. 119 for i := 0; i < maxInFlight; i++ { 120 select { 121 case <-ctx.Done(): 122 return nil, ctx.Err() 123 case runner.inFlight <- struct{}{}: 124 // Make sure we always "return" any workers we took. 125 defer func() { <-runner.inFlight }() 126 } 127 } 128 129 return inv.runWithFriendlyError(ctx, stdout, stderr) 130 } 131 132 // An Invocation represents a call to the go command. 133 type Invocation struct { 134 Verb string 135 Args []string 136 BuildFlags []string 137 138 // If ModFlag is set, the go command is invoked with -mod=ModFlag. 139 ModFlag string 140 141 // If ModFile is set, the go command is invoked with -modfile=ModFile. 142 ModFile string 143 144 // If Overlay is set, the go command is invoked with -overlay=Overlay. 145 Overlay string 146 147 // If CleanEnv is set, the invocation will run only with the environment 148 // in Env, not starting with os.Environ. 149 CleanEnv bool 150 Env []string 151 WorkingDir string 152 Logf func(format string, args ...interface{}) 153 } 154 155 func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) { 156 rawError = i.run(ctx, stdout, stderr) 157 if rawError != nil { 158 friendlyError = rawError 159 // Check for 'go' executable not being found. 160 if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound { 161 friendlyError = fmt.Errorf("go command required, not found: %v", ee) 162 } 163 if ctx.Err() != nil { 164 friendlyError = ctx.Err() 165 } 166 friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr) 167 } 168 return 169 } 170 171 func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error { 172 log := i.Logf 173 if log == nil { 174 log = func(string, ...interface{}) {} 175 } 176 177 goArgs := []string{i.Verb} 178 179 appendModFile := func() { 180 if i.ModFile != "" { 181 goArgs = append(goArgs, "-modfile="+i.ModFile) 182 } 183 } 184 appendModFlag := func() { 185 if i.ModFlag != "" { 186 goArgs = append(goArgs, "-mod="+i.ModFlag) 187 } 188 } 189 appendOverlayFlag := func() { 190 if i.Overlay != "" { 191 goArgs = append(goArgs, "-overlay="+i.Overlay) 192 } 193 } 194 195 switch i.Verb { 196 case "env", "version": 197 goArgs = append(goArgs, i.Args...) 198 case "mod": 199 // mod needs the sub-verb before flags. 200 goArgs = append(goArgs, i.Args[0]) 201 appendModFile() 202 goArgs = append(goArgs, i.Args[1:]...) 203 case "get": 204 goArgs = append(goArgs, i.BuildFlags...) 205 appendModFile() 206 goArgs = append(goArgs, i.Args...) 207 208 default: // notably list and build. 209 goArgs = append(goArgs, i.BuildFlags...) 210 appendModFile() 211 appendModFlag() 212 appendOverlayFlag() 213 goArgs = append(goArgs, i.Args...) 214 } 215 cmd := exec.Command("go", goArgs...) 216 cmd.Stdout = stdout 217 cmd.Stderr = stderr 218 // On darwin the cwd gets resolved to the real path, which breaks anything that 219 // expects the working directory to keep the original path, including the 220 // go command when dealing with modules. 221 // The Go stdlib has a special feature where if the cwd and the PWD are the 222 // same node then it trusts the PWD, so by setting it in the env for the child 223 // process we fix up all the paths returned by the go command. 224 if !i.CleanEnv { 225 cmd.Env = os.Environ() 226 } 227 cmd.Env = append(cmd.Env, i.Env...) 228 if i.WorkingDir != "" { 229 cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir) 230 cmd.Dir = i.WorkingDir 231 } 232 defer func(start time.Time) { log("%s for %v", time.Since(start), cmdDebugStr(cmd)) }(time.Now()) 233 234 return runCmdContext(ctx, cmd) 235 } 236 237 // DebugHangingGoCommands may be set by tests to enable additional 238 // instrumentation (including panics) for debugging hanging Go commands. 239 // 240 // See golang/go#54461 for details. 241 var DebugHangingGoCommands = false 242 243 // runCmdContext is like exec.CommandContext except it sends os.Interrupt 244 // before os.Kill. 245 func runCmdContext(ctx context.Context, cmd *exec.Cmd) error { 246 if err := cmd.Start(); err != nil { 247 return err 248 } 249 resChan := make(chan error, 1) 250 go func() { 251 resChan <- cmd.Wait() 252 }() 253 254 // If we're interested in debugging hanging Go commands, stop waiting after a 255 // minute and panic with interesting information. 256 if DebugHangingGoCommands { 257 select { 258 case err := <-resChan: 259 return err 260 case <-time.After(1 * time.Minute): 261 HandleHangingGoCommand(cmd.Process) 262 case <-ctx.Done(): 263 } 264 } else { 265 select { 266 case err := <-resChan: 267 return err 268 case <-ctx.Done(): 269 } 270 } 271 272 // Cancelled. Interrupt and see if it ends voluntarily. 273 cmd.Process.Signal(os.Interrupt) 274 select { 275 case err := <-resChan: 276 return err 277 case <-time.After(time.Second): 278 } 279 280 // Didn't shut down in response to interrupt. Kill it hard. 281 // TODO(rfindley): per advice from bcmills@, it may be better to send SIGQUIT 282 // on certain platforms, such as unix. 283 if err := cmd.Process.Kill(); err != nil && DebugHangingGoCommands { 284 // Don't panic here as this reliably fails on windows with EINVAL. 285 log.Printf("error killing the Go command: %v", err) 286 } 287 288 // See above: don't wait indefinitely if we're debugging hanging Go commands. 289 if DebugHangingGoCommands { 290 select { 291 case err := <-resChan: 292 return err 293 case <-time.After(10 * time.Second): // a shorter wait as resChan should return quickly following Kill 294 HandleHangingGoCommand(cmd.Process) 295 } 296 } 297 return <-resChan 298 } 299 300 func HandleHangingGoCommand(proc *os.Process) { 301 switch runtime.GOOS { 302 case "linux", "darwin", "freebsd", "netbsd": 303 fmt.Fprintln(os.Stderr, `DETECTED A HANGING GO COMMAND 304 305 The gopls test runner has detected a hanging go command. In order to debug 306 this, the output of ps and lsof/fstat is printed below. 307 308 See golang/go#54461 for more details.`) 309 310 fmt.Fprintln(os.Stderr, "\nps axo ppid,pid,command:") 311 fmt.Fprintln(os.Stderr, "-------------------------") 312 psCmd := exec.Command("ps", "axo", "ppid,pid,command") 313 psCmd.Stdout = os.Stderr 314 psCmd.Stderr = os.Stderr 315 if err := psCmd.Run(); err != nil { 316 panic(fmt.Sprintf("running ps: %v", err)) 317 } 318 319 listFiles := "lsof" 320 if runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" { 321 listFiles = "fstat" 322 } 323 324 fmt.Fprintln(os.Stderr, "\n"+listFiles+":") 325 fmt.Fprintln(os.Stderr, "-----") 326 listFilesCmd := exec.Command(listFiles) 327 listFilesCmd.Stdout = os.Stderr 328 listFilesCmd.Stderr = os.Stderr 329 if err := listFilesCmd.Run(); err != nil { 330 panic(fmt.Sprintf("running %s: %v", listFiles, err)) 331 } 332 } 333 panic(fmt.Sprintf("detected hanging go command (pid %d): see golang/go#54461 for more details", proc.Pid)) 334 } 335 336 func cmdDebugStr(cmd *exec.Cmd) string { 337 env := make(map[string]string) 338 for _, kv := range cmd.Env { 339 split := strings.SplitN(kv, "=", 2) 340 if len(split) == 2 { 341 k, v := split[0], split[1] 342 env[k] = v 343 } 344 } 345 346 var args []string 347 for _, arg := range cmd.Args { 348 quoted := strconv.Quote(arg) 349 if quoted[1:len(quoted)-1] != arg || strings.Contains(arg, " ") { 350 args = append(args, quoted) 351 } else { 352 args = append(args, arg) 353 } 354 } 355 return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v %v", env["GOROOT"], env["GOPATH"], env["GO111MODULE"], env["GOPROXY"], env["PWD"], strings.Join(args, " ")) 356 }