invoke.go (17409B)
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 "encoding/json" 12 "errors" 13 "fmt" 14 "io" 15 "log" 16 "os" 17 "os/exec" 18 "path/filepath" 19 "regexp" 20 "runtime" 21 "strconv" 22 "strings" 23 "sync" 24 "time" 25 26 "golang.org/x/tools/internal/event" 27 "golang.org/x/tools/internal/event/keys" 28 "golang.org/x/tools/internal/event/label" 29 ) 30 31 // A Runner will run go command invocations and serialize 32 // them if it sees a concurrency error. 33 type Runner struct { 34 // once guards the runner initialization. 35 once sync.Once 36 37 // inFlight tracks available workers. 38 inFlight chan struct{} 39 40 // serialized guards the ability to run a go command serially, 41 // to avoid deadlocks when claiming workers. 42 serialized chan struct{} 43 } 44 45 const maxInFlight = 10 46 47 func (runner *Runner) initialize() { 48 runner.once.Do(func() { 49 runner.inFlight = make(chan struct{}, maxInFlight) 50 runner.serialized = make(chan struct{}, 1) 51 }) 52 } 53 54 // 1.13: go: updates to go.mod needed, but contents have changed 55 // 1.14: go: updating go.mod: existing contents have changed since last read 56 var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`) 57 58 // event keys for go command invocations 59 var ( 60 verb = keys.NewString("verb", "go command verb") 61 directory = keys.NewString("directory", "") 62 ) 63 64 func invLabels(inv Invocation) []label.Label { 65 return []label.Label{verb.Of(inv.Verb), directory.Of(inv.WorkingDir)} 66 } 67 68 // Run is a convenience wrapper around RunRaw. 69 // It returns only stdout and a "friendly" error. 70 func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) { 71 ctx, done := event.Start(ctx, "gocommand.Runner.Run", invLabels(inv)...) 72 defer done() 73 74 stdout, _, friendly, _ := runner.RunRaw(ctx, inv) 75 return stdout, friendly 76 } 77 78 // RunPiped runs the invocation serially, always waiting for any concurrent 79 // invocations to complete first. 80 func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error { 81 ctx, done := event.Start(ctx, "gocommand.Runner.RunPiped", invLabels(inv)...) 82 defer done() 83 84 _, err := runner.runPiped(ctx, inv, stdout, stderr) 85 return err 86 } 87 88 // RunRaw runs the invocation, serializing requests only if they fight over 89 // go.mod changes. 90 // Postcondition: both error results have same nilness. 91 func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { 92 ctx, done := event.Start(ctx, "gocommand.Runner.RunRaw", invLabels(inv)...) 93 defer done() 94 // Make sure the runner is always initialized. 95 runner.initialize() 96 97 // First, try to run the go command concurrently. 98 stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv) 99 100 // If we encounter a load concurrency error, we need to retry serially. 101 if friendlyErr != nil && modConcurrencyError.MatchString(friendlyErr.Error()) { 102 event.Error(ctx, "Load concurrency error, will retry serially", err) 103 104 // Run serially by calling runPiped. 105 stdout.Reset() 106 stderr.Reset() 107 friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr) 108 } 109 110 return stdout, stderr, friendlyErr, err 111 } 112 113 // Postcondition: both error results have same nilness. 114 func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { 115 // Wait for 1 worker to become available. 116 select { 117 case <-ctx.Done(): 118 return nil, nil, ctx.Err(), ctx.Err() 119 case runner.inFlight <- struct{}{}: 120 defer func() { <-runner.inFlight }() 121 } 122 123 stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{} 124 friendlyErr, err := inv.runWithFriendlyError(ctx, stdout, stderr) 125 return stdout, stderr, friendlyErr, err 126 } 127 128 // Postcondition: both error results have same nilness. 129 func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) { 130 // Make sure the runner is always initialized. 131 runner.initialize() 132 133 // Acquire the serialization lock. This avoids deadlocks between two 134 // runPiped commands. 135 select { 136 case <-ctx.Done(): 137 return ctx.Err(), ctx.Err() 138 case runner.serialized <- struct{}{}: 139 defer func() { <-runner.serialized }() 140 } 141 142 // Wait for all in-progress go commands to return before proceeding, 143 // to avoid load concurrency errors. 144 for range maxInFlight { 145 select { 146 case <-ctx.Done(): 147 return ctx.Err(), ctx.Err() 148 case runner.inFlight <- struct{}{}: 149 // Make sure we always "return" any workers we took. 150 defer func() { <-runner.inFlight }() 151 } 152 } 153 154 return inv.runWithFriendlyError(ctx, stdout, stderr) 155 } 156 157 // An Invocation represents a call to the go command. 158 type Invocation struct { 159 Verb string 160 Args []string 161 BuildFlags []string 162 163 // If ModFlag is set, the go command is invoked with -mod=ModFlag. 164 // TODO(rfindley): remove, in favor of Args. 165 ModFlag string 166 167 // If ModFile is set, the go command is invoked with -modfile=ModFile. 168 // TODO(rfindley): remove, in favor of Args. 169 ModFile string 170 171 // Overlay is the name of the JSON overlay file that describes 172 // unsaved editor buffers; see [WriteOverlays]. 173 // If set, the go command is invoked with -overlay=Overlay. 174 // TODO(rfindley): remove, in favor of Args. 175 Overlay string 176 177 // If CleanEnv is set, the invocation will run only with the environment 178 // in Env, not starting with os.Environ. 179 CleanEnv bool 180 Env []string 181 WorkingDir string 182 Logf func(format string, args ...any) 183 } 184 185 // Postcondition: both error results have same nilness. 186 func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) { 187 rawError = i.run(ctx, stdout, stderr) 188 if rawError != nil { 189 friendlyError = rawError 190 // Check for 'go' executable not being found. 191 if ee, ok := rawError.(*exec.Error); ok && ee.Err == exec.ErrNotFound { 192 friendlyError = fmt.Errorf("go command required, not found: %v", ee) 193 } 194 if ctx.Err() != nil { 195 friendlyError = ctx.Err() 196 } 197 friendlyError = fmt.Errorf("err: %v: stderr: %s", friendlyError, stderr) 198 } 199 return 200 } 201 202 // logf logs if i.Logf is non-nil. 203 func (i *Invocation) logf(format string, args ...any) { 204 if i.Logf != nil { 205 i.Logf(format, args...) 206 } 207 } 208 209 func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error { 210 goArgs := []string{i.Verb} 211 212 appendModFile := func() { 213 if i.ModFile != "" { 214 goArgs = append(goArgs, "-modfile="+i.ModFile) 215 } 216 } 217 appendModFlag := func() { 218 if i.ModFlag != "" { 219 goArgs = append(goArgs, "-mod="+i.ModFlag) 220 } 221 } 222 appendOverlayFlag := func() { 223 if i.Overlay != "" { 224 goArgs = append(goArgs, "-overlay="+i.Overlay) 225 } 226 } 227 228 switch i.Verb { 229 case "env", "version": 230 goArgs = append(goArgs, i.Args...) 231 case "mod": 232 // mod needs the sub-verb before flags. 233 goArgs = append(goArgs, i.Args[0]) 234 appendModFile() 235 goArgs = append(goArgs, i.Args[1:]...) 236 case "get": 237 goArgs = append(goArgs, i.BuildFlags...) 238 appendModFile() 239 goArgs = append(goArgs, i.Args...) 240 241 default: // notably list and build. 242 goArgs = append(goArgs, i.BuildFlags...) 243 appendModFile() 244 appendModFlag() 245 appendOverlayFlag() 246 goArgs = append(goArgs, i.Args...) 247 } 248 cmd := exec.Command("go", goArgs...) 249 cmd.Stdout = stdout 250 cmd.Stderr = stderr 251 252 // https://go.dev/issue/59541: don't wait forever copying stderr 253 // after the command has exited. 254 // After CL 484741 we copy stdout manually, so we we'll stop reading that as 255 // soon as ctx is done. However, we also don't want to wait around forever 256 // for stderr. Give a much-longer-than-reasonable delay and then assume that 257 // something has wedged in the kernel or runtime. 258 cmd.WaitDelay = 30 * time.Second 259 260 // The cwd gets resolved to the real path. On Darwin, where 261 // /tmp is a symlink, this breaks anything that expects the 262 // working directory to keep the original path, including the 263 // go command when dealing with modules. 264 // 265 // os.Getwd has a special feature where if the cwd and the PWD 266 // are the same node then it trusts the PWD, so by setting it 267 // in the env for the child process we fix up all the paths 268 // returned by the go command. 269 if !i.CleanEnv { 270 cmd.Env = os.Environ() 271 } 272 cmd.Env = append(cmd.Env, i.Env...) 273 if i.WorkingDir != "" { 274 cmd.Env = append(cmd.Env, "PWD="+i.WorkingDir) 275 cmd.Dir = i.WorkingDir 276 } 277 278 debugStr := cmdDebugStr(cmd) 279 i.logf("starting %v", debugStr) 280 start := time.Now() 281 defer func() { 282 i.logf("%s for %v", time.Since(start), debugStr) 283 }() 284 285 return runCmdContext(ctx, cmd) 286 } 287 288 // DebugHangingGoCommands may be set by tests to enable additional 289 // instrumentation (including panics) for debugging hanging Go commands. 290 // 291 // See golang/go#54461 for details. 292 var DebugHangingGoCommands = false 293 294 // runCmdContext is like exec.CommandContext except it sends os.Interrupt 295 // before os.Kill. 296 func runCmdContext(ctx context.Context, cmd *exec.Cmd) (err error) { 297 // If cmd.Stdout is not an *os.File, the exec package will create a pipe and 298 // copy it to the Writer in a goroutine until the process has finished and 299 // either the pipe reaches EOF or command's WaitDelay expires. 300 // 301 // However, the output from 'go list' can be quite large, and we don't want to 302 // keep reading (and allocating buffers) if we've already decided we don't 303 // care about the output. We don't want to wait for the process to finish, and 304 // we don't wait to wait for the WaitDelay to expire either. 305 // 306 // Instead, if cmd.Stdout requires a copying goroutine we explicitly replace 307 // it with a pipe (which is an *os.File), which we can close in order to stop 308 // copying output as soon as we realize we don't care about it. 309 var stdoutW *os.File 310 if cmd.Stdout != nil { 311 if _, ok := cmd.Stdout.(*os.File); !ok { 312 var stdoutR *os.File 313 stdoutR, stdoutW, err = os.Pipe() 314 if err != nil { 315 return err 316 } 317 prevStdout := cmd.Stdout 318 cmd.Stdout = stdoutW 319 320 stdoutErr := make(chan error, 1) 321 go func() { 322 _, err := io.Copy(prevStdout, stdoutR) 323 if err != nil { 324 err = fmt.Errorf("copying stdout: %w", err) 325 } 326 stdoutErr <- err 327 }() 328 defer func() { 329 // We started a goroutine to copy a stdout pipe. 330 // Wait for it to finish, or terminate it if need be. 331 var err2 error 332 select { 333 case err2 = <-stdoutErr: 334 stdoutR.Close() 335 case <-ctx.Done(): 336 stdoutR.Close() 337 // Per https://pkg.go.dev/os#File.Close, the call to stdoutR.Close 338 // should cause the Read call in io.Copy to unblock and return 339 // immediately, but we still need to receive from stdoutErr to confirm 340 // that it has happened. 341 <-stdoutErr 342 err2 = ctx.Err() 343 } 344 if err == nil { 345 err = err2 346 } 347 }() 348 349 // Per https://pkg.go.dev/os/exec#Cmd, “If Stdout and Stderr are the 350 // same writer, and have a type that can be compared with ==, at most 351 // one goroutine at a time will call Write.” 352 // 353 // Since we're starting a goroutine that writes to cmd.Stdout, we must 354 // also update cmd.Stderr so that it still holds. 355 func() { 356 defer func() { recover() }() 357 if cmd.Stderr == prevStdout { 358 cmd.Stderr = cmd.Stdout 359 } 360 }() 361 } 362 } 363 364 startTime := time.Now() 365 err = cmd.Start() 366 if stdoutW != nil { 367 // The child process has inherited the pipe file, 368 // so close the copy held in this process. 369 stdoutW.Close() 370 stdoutW = nil 371 } 372 if err != nil { 373 return err 374 } 375 376 resChan := make(chan error, 1) 377 go func() { 378 resChan <- cmd.Wait() 379 }() 380 381 // If we're interested in debugging hanging Go commands, stop waiting after a 382 // minute and panic with interesting information. 383 debug := DebugHangingGoCommands 384 if debug { 385 timer := time.NewTimer(1 * time.Minute) 386 defer timer.Stop() 387 select { 388 case err := <-resChan: 389 return err 390 case <-timer.C: 391 // HandleHangingGoCommand terminates this process. 392 // Pass off resChan in case we can collect the command error. 393 handleHangingGoCommand(startTime, cmd, resChan) 394 case <-ctx.Done(): 395 } 396 } else { 397 select { 398 case err := <-resChan: 399 return err 400 case <-ctx.Done(): 401 } 402 } 403 404 // Cancelled. Interrupt and see if it ends voluntarily. 405 if err := cmd.Process.Signal(os.Interrupt); err == nil { 406 // (We used to wait only 1s but this proved 407 // fragile on loaded builder machines.) 408 timer := time.NewTimer(5 * time.Second) 409 defer timer.Stop() 410 select { 411 case err := <-resChan: 412 return err 413 case <-timer.C: 414 } 415 } 416 417 // Didn't shut down in response to interrupt. Kill it hard. 418 if err := cmd.Process.Kill(); err != nil && !errors.Is(err, os.ErrProcessDone) && debug { 419 log.Printf("error killing the Go command: %v", err) 420 } 421 422 return <-resChan 423 } 424 425 // handleHangingGoCommand outputs debugging information to help diagnose the 426 // cause of a hanging Go command, and then exits with log.Fatalf. 427 func handleHangingGoCommand(start time.Time, cmd *exec.Cmd, resChan chan error) { 428 switch runtime.GOOS { 429 case "linux", "darwin", "freebsd", "netbsd", "openbsd": 430 fmt.Fprintln(os.Stderr, `DETECTED A HANGING GO COMMAND 431 432 The gopls test runner has detected a hanging go command. In order to debug 433 this, the output of ps and lsof/fstat is printed below. 434 435 See golang/go#54461 for more details.`) 436 437 fmt.Fprintln(os.Stderr, "\nps axo ppid,pid,command:") 438 fmt.Fprintln(os.Stderr, "-------------------------") 439 psCmd := exec.Command("ps", "axo", "ppid,pid,command") 440 psCmd.Stdout = os.Stderr 441 psCmd.Stderr = os.Stderr 442 if err := psCmd.Run(); err != nil { 443 log.Printf("Handling hanging Go command: running ps: %v", err) 444 } 445 446 listFiles := "lsof" 447 if runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" { 448 listFiles = "fstat" 449 } 450 451 fmt.Fprintln(os.Stderr, "\n"+listFiles+":") 452 fmt.Fprintln(os.Stderr, "-----") 453 listFilesCmd := exec.Command(listFiles) 454 listFilesCmd.Stdout = os.Stderr 455 listFilesCmd.Stderr = os.Stderr 456 if err := listFilesCmd.Run(); err != nil { 457 log.Printf("Handling hanging Go command: running %s: %v", listFiles, err) 458 } 459 // Try to extract information about the slow go process by issuing a SIGQUIT. 460 if err := cmd.Process.Signal(sigStuckProcess); err == nil { 461 select { 462 case err := <-resChan: 463 stderr := "not a bytes.Buffer" 464 if buf, _ := cmd.Stderr.(*bytes.Buffer); buf != nil { 465 stderr = buf.String() 466 } 467 log.Printf("Quit hanging go command:\n\terr:%v\n\tstderr:\n%v\n\n", err, stderr) 468 case <-time.After(5 * time.Second): 469 } 470 } else { 471 log.Printf("Sending signal %d to hanging go command: %v", sigStuckProcess, err) 472 } 473 } 474 log.Fatalf("detected hanging go command (golang/go#54461); waited %s\n\tcommand:%s\n\tpid:%d", time.Since(start), cmd, cmd.Process.Pid) 475 } 476 477 func cmdDebugStr(cmd *exec.Cmd) string { 478 env := make(map[string]string) 479 for _, kv := range cmd.Env { 480 split := strings.SplitN(kv, "=", 2) 481 if len(split) == 2 { 482 k, v := split[0], split[1] 483 env[k] = v 484 } 485 } 486 487 var args []string 488 for _, arg := range cmd.Args { 489 quoted := strconv.Quote(arg) 490 if quoted[1:len(quoted)-1] != arg || strings.Contains(arg, " ") { 491 args = append(args, quoted) 492 } else { 493 args = append(args, arg) 494 } 495 } 496 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, " ")) 497 } 498 499 // WriteOverlays writes each value in the overlay (see the Overlay 500 // field of go/packages.Config) to a temporary file and returns the name 501 // of a JSON file describing the mapping that is suitable for the "go 502 // list -overlay" flag. 503 // 504 // On success, the caller must call the cleanup function exactly once 505 // when the files are no longer needed. 506 func WriteOverlays(overlay map[string][]byte) (filename string, cleanup func(), err error) { 507 // Do nothing if there are no overlays in the config. 508 if len(overlay) == 0 { 509 return "", func() {}, nil 510 } 511 512 dir, err := os.MkdirTemp("", "gocommand-*") 513 if err != nil { 514 return "", nil, err 515 } 516 517 // The caller must clean up this directory, 518 // unless this function returns an error. 519 // (The cleanup operand of each return 520 // statement below is ignored.) 521 defer func() { 522 cleanup = func() { 523 os.RemoveAll(dir) 524 } 525 if err != nil { 526 cleanup() 527 cleanup = nil 528 } 529 }() 530 531 // Write each map entry to a temporary file. 532 overlays := make(map[string]string) 533 for k, v := range overlay { 534 // Use a unique basename for each file (001-foo.go), 535 // to avoid creating nested directories. 536 base := fmt.Sprintf("%d-%s", 1+len(overlays), filepath.Base(k)) 537 filename := filepath.Join(dir, base) 538 err := os.WriteFile(filename, v, 0666) 539 if err != nil { 540 return "", nil, err 541 } 542 overlays[k] = filename 543 } 544 545 // Write the JSON overlay file that maps logical file names to temp files. 546 // 547 // OverlayJSON is the format overlay files are expected to be in. 548 // The Replace map maps from overlaid paths to replacement paths: 549 // the Go command will forward all reads trying to open 550 // each overlaid path to its replacement path, or consider the overlaid 551 // path not to exist if the replacement path is empty. 552 // 553 // From golang/go#39958. 554 type OverlayJSON struct { 555 Replace map[string]string `json:"replace,omitempty"` 556 } 557 b, err := json.Marshal(OverlayJSON{Replace: overlays}) 558 if err != nil { 559 return "", nil, err 560 } 561 filename = filepath.Join(dir, "overlay.json") 562 if err := os.WriteFile(filename, b, 0666); err != nil { 563 return "", nil, err 564 } 565 566 return filename, nil, nil 567 }