// 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 gocommandimport ()// A Runner will run go command invocations and serialize// them if it sees a concurrency error.typeRunnerstruct {// once guards the runner initialization.oncesync.Once// inFlight tracks available workers.inFlightchanstruct{}// serialized guards the ability to run a go command serially, // to avoid deadlocks when claiming workers.serializedchanstruct{}}constmaxInFlight = 10func ( *Runner) () { .once.Do(func() { .inFlight = make(chanstruct{}, maxInFlight) .serialized = make(chanstruct{}, 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 readvarmodConcurrencyError = regexp.MustCompile(`go:.*go.mod.*contents have changed`)// event keys for go command invocationsvar (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():returnnil, nil, .Err(), .Err()case .inFlight<-struct{}{}:deferfunc() { <-.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{}{}:deferfunc() { <-.serialized }() }// Wait for all in-progress go commands to return before proceeding, // to avoid load concurrency errors.forrangemaxInFlight {select {case<-.Done():return .Err(), .Err()case .inFlight<-struct{}{}:// Make sure we always "return" any workers we took.deferfunc() { <-.inFlight }() } }return .runWithFriendlyError(, , )}// An Invocation represents a call to the go command.typeInvocationstruct {VerbstringArgs []stringBuildFlags []string// If ModFlag is set, the go command is invoked with -mod=ModFlag. // TODO(rfindley): remove, in favor of Args.ModFlagstring// If ModFile is set, the go command is invoked with -modfile=ModFile. // TODO(rfindley): remove, in favor of Args.ModFilestring// 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.Overlaystring// If CleanEnv is set, the invocation will run only with the environment // in Env, not starting with os.Environ.CleanEnvboolEnv []stringWorkingDirstringLogffunc(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()deferfunc() { .logf("%s for %v", time.Since(), ) }()returnrunCmdContext(, )}// DebugHangingGoCommands may be set by tests to enable additional// instrumentation (including panics) for debugging hanging Go commands.//// See golang/go#54461 for details.varDebugHangingGoCommands = 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.Fileif .Stdout != nil {if , := .Stdout.(*os.File); ! {var *os.File , , = os.Pipe()if != nil {return } := .Stdout .Stdout = := make(chanerror, 1)gofunc() { , := io.Copy(, )if != nil { = fmt.Errorf("copying stdout: %w", ) } <- }()deferfunc() {// We started a goroutine to copy a stdout pipe. // Wait for it to finish, or terminate it if need be.varerrorselect {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() {deferfunc() { 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(chanerror, 1)gofunc() { <- .Wait() }()// If we're interested in debugging hanging Go commands, stop waiting after a // minute and panic with interesting information. := DebugHangingGoCommandsif { := time.NewTimer(1 * time.Minute)defer .Stop()select {case := <-:returncase<-.C:// HandleHangingGoCommand terminates this process. // Pass off resChan in case we can collect the command error.handleHangingGoCommand(, , )case<-.Done(): } } else {select {case := <-:returncase<-.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 := <-:returncase<-.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, chanerror) {switchruntime.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.Stderrif := .Run(); != nil {log.Printf("Handling hanging Go command: running ps: %v", ) } := "lsof"ifruntime.GOOS == "freebsd" || runtime.GOOS == "netbsd" { = "fstat" }fmt.Fprintln(os.Stderr, "\n"++":")fmt.Fprintln(os.Stderr, "-----") := exec.Command() .Stdout = os.Stderr .Stderr = os.Stderrif := .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)iflen() == 2 { , := [0], [1] [] = } }var []stringfor , := range .Args { := strconv.Quote()if [1:len()-1] != || strings.Contains(, " ") { = append(, ) } else { = append(, ) } }returnfmt.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.iflen() == 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.)deferfunc() { = 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.typestruct {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}
The pages are generated with Goldsv0.7.6. (GOOS=linux GOARCH=amd64)