// Copyright 2020 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// Package gocommand is a helper for calling the go command.
package gocommand import ( exec ) // An Runner will run go command invocations and serialize // them if it sees a concurrency error. type Runner struct { // once guards the runner initialization. once sync.Once // inFlight tracks available workers. inFlight chan struct{} // serialized guards the ability to run a go command serially, // to avoid deadlocks when claiming workers. serialized chan struct{} } const maxInFlight = 10 func ( *Runner) () { .once.Do(func() { .inFlight = make(chan struct{}, maxInFlight) .serialized = make(chan struct{}, 1) }) } // 1.13: go: updates to go.mod needed, but contents have changed // 1.14: go: updating go.mod: existing contents have changed since last read var modConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`) // Run is a convenience wrapper around RunRaw. // It returns only stdout and a "friendly" error. func ( *Runner) ( context.Context, Invocation) (*bytes.Buffer, error) { , , , := .RunRaw(, ) return , } // RunPiped runs the invocation serially, always waiting for any concurrent // invocations to complete first. func ( *Runner) ( context.Context, Invocation, , io.Writer) error { , := .runPiped(, , , ) return } // RunRaw runs the invocation, serializing requests only if they fight over // go.mod changes. func ( *Runner) ( context.Context, Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { // Make sure the runner is always initialized. .initialize() // First, try to run the go command concurrently. , , , := .runConcurrent(, ) // If we encounter a load concurrency error, we need to retry serially. if == nil || !modConcurrencyError.MatchString(.Error()) { return , , , } event.Error(, "Load concurrency error, will retry serially", ) // Run serially by calling runPiped. .Reset() .Reset() , = .runPiped(, , , ) return , , , } func ( *Runner) ( context.Context, Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { // Wait for 1 worker to become available. select { case <-.Done(): return nil, nil, nil, .Err() case .inFlight <- struct{}{}: defer func() { <-.inFlight }() } , := &bytes.Buffer{}, &bytes.Buffer{} , := .runWithFriendlyError(, , ) return , , , } func ( *Runner) ( context.Context, Invocation, , io.Writer) (error, error) { // Make sure the runner is always initialized. .initialize() // Acquire the serialization lock. This avoids deadlocks between two // runPiped commands. select { case <-.Done(): return nil, .Err() case .serialized <- struct{}{}: defer func() { <-.serialized }() } // Wait for all in-progress go commands to return before proceeding, // to avoid load concurrency errors. for := 0; < maxInFlight; ++ { select { case <-.Done(): return nil, .Err() case .inFlight <- struct{}{}: // Make sure we always "return" any workers we took. defer func() { <-.inFlight }() } } return .runWithFriendlyError(, , ) } // An Invocation represents a call to the go command. type Invocation struct { Verb string Args []string BuildFlags []string // If ModFlag is set, the go command is invoked with -mod=ModFlag. ModFlag string // If ModFile is set, the go command is invoked with -modfile=ModFile. ModFile string // If Overlay is set, the go command is invoked with -overlay=Overlay. Overlay string // If CleanEnv is set, the invocation will run only with the environment // in Env, not starting with os.Environ. CleanEnv bool Env []string WorkingDir string Logf func(format string, args ...interface{}) } func ( *Invocation) ( context.Context, , io.Writer) ( error, error) { = .run(, , ) if != nil { = // Check for 'go' executable not being found. if , := .(*exec.Error); && .Err == exec.ErrNotFound { = fmt.Errorf("go command required, not found: %v", ) } if .Err() != nil { = .Err() } = fmt.Errorf("err: %v: stderr: %s", , ) } return } func ( *Invocation) ( context.Context, , io.Writer) error { := .Logf if == nil { = func(string, ...interface{}) {} } := []string{.Verb} := func() { if .ModFile != "" { = append(, "-modfile="+.ModFile) } } := func() { if .ModFlag != "" { = append(, "-mod="+.ModFlag) } } := func() { if .Overlay != "" { = append(, "-overlay="+.Overlay) } } switch .Verb { case "env", "version": = append(, .Args...) case "mod": // mod needs the sub-verb before flags. = append(, .Args[0]) () = append(, .Args[1:]...) case "get": = append(, .BuildFlags...) () = append(, .Args...) default: // notably list and build. = append(, .BuildFlags...) () () () = append(, .Args...) } := exec.Command("go", ...) .Stdout = .Stderr = // On darwin the cwd gets resolved to the real path, which breaks anything that // expects the working directory to keep the original path, including the // go command when dealing with modules. // The Go stdlib has a special feature where if the cwd and the PWD are the // same node then it trusts the PWD, so by setting it in the env for the child // process we fix up all the paths returned by the go command. if !.CleanEnv { .Env = os.Environ() } .Env = append(.Env, .Env...) if .WorkingDir != "" { .Env = append(.Env, "PWD="+.WorkingDir) .Dir = .WorkingDir } defer func( time.Time) { ("%s for %v", time.Since(), cmdDebugStr()) }(time.Now()) return runCmdContext(, ) } // DebugHangingGoCommands may be set by tests to enable additional // instrumentation (including panics) for debugging hanging Go commands. // // See golang/go#54461 for details. var DebugHangingGoCommands = false // runCmdContext is like exec.CommandContext except it sends os.Interrupt // before os.Kill. func ( context.Context, *exec.Cmd) error { if := .Start(); != nil { return } := make(chan error, 1) go func() { <- .Wait() }() // If we're interested in debugging hanging Go commands, stop waiting after a // minute and panic with interesting information. if DebugHangingGoCommands { select { case := <-: return case <-time.After(1 * time.Minute): HandleHangingGoCommand(.Process) case <-.Done(): } } else { select { case := <-: return case <-.Done(): } } // Cancelled. Interrupt and see if it ends voluntarily. .Process.Signal(os.Interrupt) select { case := <-: return case <-time.After(time.Second): } // Didn't shut down in response to interrupt. Kill it hard. // TODO(rfindley): per advice from bcmills@, it may be better to send SIGQUIT // on certain platforms, such as unix. if := .Process.Kill(); != nil && DebugHangingGoCommands { // Don't panic here as this reliably fails on windows with EINVAL. log.Printf("error killing the Go command: %v", ) } // See above: don't wait indefinitely if we're debugging hanging Go commands. if DebugHangingGoCommands { select { case := <-: return case <-time.After(10 * time.Second): // a shorter wait as resChan should return quickly following Kill HandleHangingGoCommand(.Process) } } return <- } func ( *os.Process) { switch runtime.GOOS { case "linux", "darwin", "freebsd", "netbsd": fmt.Fprintln(os.Stderr, `DETECTED A HANGING GO COMMAND The gopls test runner has detected a hanging go command. In order to debug this, the output of ps and lsof/fstat is printed below. See golang/go#54461 for more details.`) fmt.Fprintln(os.Stderr, "\nps axo ppid,pid,command:") fmt.Fprintln(os.Stderr, "-------------------------") := exec.Command("ps", "axo", "ppid,pid,command") .Stdout = os.Stderr .Stderr = os.Stderr if := .Run(); != nil { panic(fmt.Sprintf("running ps: %v", )) } := "lsof" if runtime.GOOS == "freebsd" || runtime.GOOS == "netbsd" { = "fstat" } fmt.Fprintln(os.Stderr, "\n"++":") fmt.Fprintln(os.Stderr, "-----") := exec.Command() .Stdout = os.Stderr .Stderr = os.Stderr if := .Run(); != nil { panic(fmt.Sprintf("running %s: %v", , )) } } panic(fmt.Sprintf("detected hanging go command (pid %d): see golang/go#54461 for more details", .Pid)) } func ( *exec.Cmd) string { := make(map[string]string) for , := range .Env { := strings.SplitN(, "=", 2) if len() == 2 { , := [0], [1] [] = } } var []string for , := range .Args { := strconv.Quote() if [1:len()-1] != || strings.Contains(, " ") { = append(, ) } else { = append(, ) } } return fmt.Sprintf("GOROOT=%v GOPATH=%v GO111MODULE=%v GOPROXY=%v PWD=%v %v", ["GOROOT"], ["GOPATH"], ["GO111MODULE"], ["GOPROXY"], ["PWD"], strings.Join(, " ")) }