// 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 ( ) // A 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`) // event keys for go command invocations var ( verb = keys.NewString("verb", "go command verb") directory = keys.NewString("directory", "") ) func ( Invocation) []label.Label { return []label.Label{verb.Of(.Verb), directory.Of(.WorkingDir)} } // Run is a convenience wrapper around RunRaw. // It returns only stdout and a "friendly" error. func ( *Runner) ( context.Context, Invocation) (*bytes.Buffer, error) { , := event.Start(, "gocommand.Runner.Run", invLabels()...) defer () , , , := .RunRaw(, ) return , } // RunPiped runs the invocation serially, always waiting for any concurrent // invocations to complete first. func ( *Runner) ( context.Context, Invocation, , io.Writer) error { , := event.Start(, "gocommand.Runner.RunPiped", invLabels()...) defer () , := .runPiped(, , , ) return } // RunRaw runs the invocation, serializing requests only if they fight over // go.mod changes. // Postcondition: both error results have same nilness. func ( *Runner) ( context.Context, Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { , := event.Start(, "gocommand.Runner.RunRaw", invLabels()...) defer () // 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()) { event.Error(, "Load concurrency error, will retry serially", ) // Run serially by calling runPiped. .Reset() .Reset() , = .runPiped(, , , ) } return , , , } // Postcondition: both error results have same nilness. func ( *Runner) ( context.Context, Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) { // Wait for 1 worker to become available. select { case <-.Done(): return nil, nil, .Err(), .Err() case .inFlight <- struct{}{}: defer func() { <-.inFlight }() } , := &bytes.Buffer{}, &bytes.Buffer{} , := .runWithFriendlyError(, , ) return , , , } // Postcondition: both error results have same nilness. 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 .Err(), .Err() case .serialized <- struct{}{}: defer func() { <-.serialized }() } // Wait for all in-progress go commands to return before proceeding, // to avoid load concurrency errors. for range maxInFlight { select { case <-.Done(): return .Err(), .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. // TODO(rfindley): remove, in favor of Args. ModFlag string // If ModFile is set, the go command is invoked with -modfile=ModFile. // TODO(rfindley): remove, in favor of Args. ModFile string // Overlay is the name of the JSON overlay file that describes // unsaved editor buffers; see [WriteOverlays]. // If set, the go command is invoked with -overlay=Overlay. // TODO(rfindley): remove, in favor of Args. 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 ...any) } // Postcondition: both error results have same nilness. 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 } // logf logs if i.Logf is non-nil. func ( *Invocation) ( string, ...any) { if .Logf != nil { .Logf(, ...) } } func ( *Invocation) ( context.Context, , io.Writer) error { := []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 = // https://go.dev/issue/59541: don't wait forever copying stderr // after the command has exited. // After CL 484741 we copy stdout manually, so we we'll stop reading that as // soon as ctx is done. However, we also don't want to wait around forever // for stderr. Give a much-longer-than-reasonable delay and then assume that // something has wedged in the kernel or runtime. .WaitDelay = 30 * time.Second // The cwd gets resolved to the real path. On Darwin, where // /tmp is a symlink, this breaks anything that expects the // working directory to keep the original path, including the // go command when dealing with modules. // // os.Getwd 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 } := cmdDebugStr() .logf("starting %v", ) := time.Now() defer func() { .logf("%s for %v", time.Since(), ) }() 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 cmd.Stdout is not an *os.File, the exec package will create a pipe and // copy it to the Writer in a goroutine until the process has finished and // either the pipe reaches EOF or command's WaitDelay expires. // // However, the output from 'go list' can be quite large, and we don't want to // keep reading (and allocating buffers) if we've already decided we don't // care about the output. We don't want to wait for the process to finish, and // we don't wait to wait for the WaitDelay to expire either. // // Instead, if cmd.Stdout requires a copying goroutine we explicitly replace // it with a pipe (which is an *os.File), which we can close in order to stop // copying output as soon as we realize we don't care about it. var *os.File if .Stdout != nil { if , := .Stdout.(*os.File); ! { var *os.File , , = os.Pipe() if != nil { return } := .Stdout .Stdout = := make(chan error, 1) go func() { , := io.Copy(, ) if != nil { = fmt.Errorf("copying stdout: %w", ) } <- }() defer func() { // We started a goroutine to copy a stdout pipe. // Wait for it to finish, or terminate it if need be. var error select { case = <-: .Close() case <-.Done(): .Close() // Per https://pkg.go.dev/os#File.Close, the call to stdoutR.Close // should cause the Read call in io.Copy to unblock and return // immediately, but we still need to receive from stdoutErr to confirm // that it has happened. <- = .Err() } if == nil { = } }() // Per https://pkg.go.dev/os/exec#Cmd, “If Stdout and Stderr are the // same writer, and have a type that can be compared with ==, at most // one goroutine at a time will call Write.” // // Since we're starting a goroutine that writes to cmd.Stdout, we must // also update cmd.Stderr so that it still holds. func() { defer func() { recover() }() if .Stderr == { .Stderr = .Stdout } }() } } := time.Now() = .Start() if != nil { // The child process has inherited the pipe file, // so close the copy held in this process. .Close() = nil } if != 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. := DebugHangingGoCommands if { := time.NewTimer(1 * time.Minute) defer .Stop() select { case := <-: return case <-.C: // HandleHangingGoCommand terminates this process. // Pass off resChan in case we can collect the command error. handleHangingGoCommand(, , ) case <-.Done(): } } else { select { case := <-: return case <-.Done(): } } // Cancelled. Interrupt and see if it ends voluntarily. if := .Process.Signal(os.Interrupt); == nil { // (We used to wait only 1s but this proved // fragile on loaded builder machines.) := time.NewTimer(5 * time.Second) defer .Stop() select { case := <-: return case <-.C: } } // Didn't shut down in response to interrupt. Kill it hard. if := .Process.Kill(); != nil && !errors.Is(, os.ErrProcessDone) && { log.Printf("error killing the Go command: %v", ) } return <- } // handleHangingGoCommand outputs debugging information to help diagnose the // cause of a hanging Go command, and then exits with log.Fatalf. func ( time.Time, *exec.Cmd, chan error) { switch runtime.GOOS { case "linux", "darwin", "freebsd", "netbsd", "openbsd": 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 { log.Printf("Handling hanging Go command: 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 { log.Printf("Handling hanging Go command: running %s: %v", , ) } // Try to extract information about the slow go process by issuing a SIGQUIT. if := .Process.Signal(sigStuckProcess); == nil { select { case := <-: := "not a bytes.Buffer" if , := .Stderr.(*bytes.Buffer); != nil { = .String() } log.Printf("Quit hanging go command:\n\terr:%v\n\tstderr:\n%v\n\n", , ) case <-time.After(5 * time.Second): } } else { log.Printf("Sending signal %d to hanging go command: %v", sigStuckProcess, ) } } log.Fatalf("detected hanging go command (golang/go#54461); waited %s\n\tcommand:%s\n\tpid:%d", time.Since(), , .Process.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(, " ")) } // WriteOverlays writes each value in the overlay (see the Overlay // field of go/packages.Config) to a temporary file and returns the name // of a JSON file describing the mapping that is suitable for the "go // list -overlay" flag. // // On success, the caller must call the cleanup function exactly once // when the files are no longer needed. func ( map[string][]byte) ( string, func(), error) { // Do nothing if there are no overlays in the config. if len() == 0 { return "", func() {}, nil } , := os.MkdirTemp("", "gocommand-*") if != nil { return "", nil, } // The caller must clean up this directory, // unless this function returns an error. // (The cleanup operand of each return // statement below is ignored.) defer func() { = func() { os.RemoveAll() } if != nil { () = nil } }() // Write each map entry to a temporary file. := make(map[string]string) for , := range { // Use a unique basename for each file (001-foo.go), // to avoid creating nested directories. := fmt.Sprintf("%d-%s", 1+len(), filepath.Base()) := filepath.Join(, ) := os.WriteFile(, , 0666) if != nil { return "", nil, } [] = } // Write the JSON overlay file that maps logical file names to temp files. // // OverlayJSON is the format overlay files are expected to be in. // The Replace map maps from overlaid paths to replacement paths: // the Go command will forward all reads trying to open // each overlaid path to its replacement path, or consider the overlaid // path not to exist if the replacement path is empty. // // From golang/go#39958. type struct { map[string]string `json:"replace,omitempty"` } , := json.Marshal({: }) if != nil { return "", nil, } = filepath.Join(, "overlay.json") if := os.WriteFile(, , 0666); != nil { return "", nil, } return , nil, nil }