GoPLS Viewer

Home|gopls/internal/gocommand/invoke.go
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.
6package gocommand
7
8import (
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.
29type 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
41const maxInFlight = 10
42
43func (runner *Runnerinitialize() {
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
52var 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.
56func (runner *RunnerRun(ctx context.Contextinv Invocation) (*bytes.Buffererror) {
57    stdout_friendly_ := runner.RunRaw(ctxinv)
58    return stdoutfriendly
59}
60
61// RunPiped runs the invocation serially, always waiting for any concurrent
62// invocations to complete first.
63func (runner *RunnerRunPiped(ctx context.Contextinv Invocationstdoutstderr io.Writererror {
64    _err := runner.runPiped(ctxinvstdoutstderr)
65    return err
66}
67
68// RunRaw runs the invocation, serializing requests only if they fight over
69// go.mod changes.
70func (runner *RunnerRunRaw(ctx context.Contextinv Invocation) (*bytes.Buffer, *bytes.Buffererrorerror) {
71    // Make sure the runner is always initialized.
72    runner.initialize()
73
74    // First, try to run the go command concurrently.
75    stdoutstderrfriendlyErrerr := runner.runConcurrent(ctxinv)
76
77    // If we encounter a load concurrency error, we need to retry serially.
78    if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) {
79        return stdoutstderrfriendlyErrerr
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    friendlyErrerr = runner.runPiped(ctxinvstdoutstderr)
87    return stdoutstderrfriendlyErrerr
88}
89
90func (runner *RunnerrunConcurrent(ctx context.Contextinv Invocation) (*bytes.Buffer, *bytes.Buffererrorerror) {
91    // Wait for 1 worker to become available.
92    select {
93    case <-ctx.Done():
94        return nilnilnilctx.Err()
95    case runner.inFlight <- struct{}{}:
96        defer func() { <-runner.inFlight }()
97    }
98
99    stdoutstderr := &bytes.Buffer{}, &bytes.Buffer{}
100    friendlyErrerr := inv.runWithFriendlyError(ctxstdoutstderr)
101    return stdoutstderrfriendlyErrerr
102}
103
104func (runner *RunnerrunPiped(ctx context.Contextinv Invocationstdoutstderr io.Writer) (errorerror) {
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 nilctx.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 := 0i < maxInFlighti++ {
120        select {
121        case <-ctx.Done():
122            return nilctx.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(ctxstdoutstderr)
130}
131
132// An Invocation represents a call to the go command.
133type 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 stringargs ...interface{})
153}
154
155func (i *InvocationrunWithFriendlyError(ctx context.Contextstdoutstderr io.Writer) (friendlyError errorrawError error) {
156    rawError = i.run(ctxstdoutstderr)
157    if rawError != nil {
158        friendlyError = rawError
159        // Check for 'go' executable not being found.
160        if eeok := 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"friendlyErrorstderr)
167    }
168    return
169}
170
171func (i *Invocationrun(ctx context.Contextstdoutstderr io.Writererror {
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(goArgsi.Args...)
198    case "mod":
199        // mod needs the sub-verb before flags.
200        goArgs = append(goArgsi.Args[0])
201        appendModFile()
202        goArgs = append(goArgsi.Args[1:]...)
203    case "get":
204        goArgs = append(goArgsi.BuildFlags...)
205        appendModFile()
206        goArgs = append(goArgsi.Args...)
207
208    default: // notably list and build.
209        goArgs = append(goArgsi.BuildFlags...)
210        appendModFile()
211        appendModFlag()
212        appendOverlayFlag()
213        goArgs = append(goArgsi.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.Envi.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(ctxcmd)
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.
241var DebugHangingGoCommands = false
242
243// runCmdContext is like exec.CommandContext except it sends os.Interrupt
244// before os.Kill.
245func runCmdContext(ctx context.Contextcmd *exec.Cmderror {
246    if err := cmd.Start(); err != nil {
247        return err
248    }
249    resChan := make(chan error1)
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
300func 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
305The gopls test runner has detected a hanging go command. In order to debug
306this, the output of ps and lsof/fstat is printed below.
307
308See 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"listFileserr))
331        }
332    }
333    panic(fmt.Sprintf("detected hanging go command (pid %d): see golang/go#54461 for more details"proc.Pid))
334}
335
336func cmdDebugStr(cmd *exec.Cmdstring {
337    env := make(map[string]string)
338    for _kv := range cmd.Env {
339        split := strings.SplitN(kv"="2)
340        if len(split) == 2 {
341            kv := 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(argsquoted)
351        } else {
352            args = append(argsarg)
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}
357
MembersX
strings
sync
Runner.Run.stdout
Runner.RunRaw.err
Runner.runPiped.ctx
Runner.runPiped.i
Invocation.CleanEnv
DebugHangingGoCommands
runCmdContext.err
cmdDebugStr.env
Runner.runConcurrent.err
Invocation.run.goArgs
Runner.Run
Runner.Run._
Runner.runConcurrent.runner
Runner.initialize
Runner.RunRaw.friendlyErr
os
Runner.RunPiped.inv
Runner.RunPiped.err
Runner.RunRaw.runner
Runner.runConcurrent.stdout
Invocation.Logf
Runner.inFlight
Runner.Run.inv
Invocation.BuildFlags
Invocation.runWithFriendlyError
HandleHangingGoCommand.proc
Runner
Runner.RunPiped
Runner.runConcurrent.inv
Invocation.runWithFriendlyError.stdout
Runner.Run.runner
Runner.RunRaw.ctx
Invocation.Env
runCmdContext
Runner.Run.friendly
runCmdContext.cmd
regexp
Runner.initialize.runner
Invocation.run.stdout
Invocation.run.log
HandleHangingGoCommand
cmdDebugStr.RangeStmt_9974.BlockStmt.split
strconv
Runner.serialized
runCmdContext.BlockStmt.err
HandleHangingGoCommand.BlockStmt.listFiles
Runner.RunPiped.runner
Invocation.Args
log
Runner.RunPiped.stderr
Invocation.Overlay
HandleHangingGoCommand.BlockStmt.listFilesCmd
cmdDebugStr
cmdDebugStr.RangeStmt_10136.arg
Runner.runPiped.runner
Runner.runPiped.inv
Invocation.run.i
runCmdContext.BlockStmt.BlockStmt.err
cmdDebugStr.RangeStmt_9974.kv
bytes
fmt
exec
Runner.runPiped.stderr
Invocation.runWithFriendlyError.ctx
Invocation.runWithFriendlyError.stderr
Invocation.run.ctx
HandleHangingGoCommand.BlockStmt.psCmd
Runner.Run.ctx
Invocation.runWithFriendlyError.friendlyError
Runner.runConcurrent.friendlyErr
Invocation
Runner.RunPiped.stdout
Invocation.run.cmd
cmdDebugStr.cmd
Runner.RunPiped.ctx
Invocation.ModFlag
Invocation.runWithFriendlyError.rawError
Invocation.run.stderr
HandleHangingGoCommand.BlockStmt.err
context
runtime
Runner.runPiped
runCmdContext.ctx
Runner.RunRaw.inv
Runner.runConcurrent.stderr
Invocation.runWithFriendlyError.i
cmdDebugStr.RangeStmt_10136.BlockStmt.quoted
Runner.RunRaw
Runner.RunRaw.stdout
Runner.RunRaw.stderr
runCmdContext.resChan
Runner.runPiped.stdout
Invocation.Verb
time
event
Runner.RunPiped._
Runner.runConcurrent.ctx
Invocation.ModFile
Invocation.WorkingDir
Invocation.run
io
Runner.once
maxInFlight
Runner.runConcurrent
cmdDebugStr.args
Members
X