gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

invoke.go (10526B)


      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 	"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.
     29 type 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 
     41 const maxInFlight = 10
     42 
     43 func (runner *Runner) initialize() {
     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
     52 var 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.
     56 func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) {
     57 	stdout, _, friendly, _ := runner.RunRaw(ctx, inv)
     58 	return stdout, friendly
     59 }
     60 
     61 // RunPiped runs the invocation serially, always waiting for any concurrent
     62 // invocations to complete first.
     63 func (runner *Runner) RunPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) error {
     64 	_, err := runner.runPiped(ctx, inv, stdout, stderr)
     65 	return err
     66 }
     67 
     68 // RunRaw runs the invocation, serializing requests only if they fight over
     69 // go.mod changes.
     70 func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
     71 	// Make sure the runner is always initialized.
     72 	runner.initialize()
     73 
     74 	// First, try to run the go command concurrently.
     75 	stdout, stderr, friendlyErr, err := runner.runConcurrent(ctx, inv)
     76 
     77 	// If we encounter a load concurrency error, we need to retry serially.
     78 	if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) {
     79 		return stdout, stderr, friendlyErr, err
     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 	friendlyErr, err = runner.runPiped(ctx, inv, stdout, stderr)
     87 	return stdout, stderr, friendlyErr, err
     88 }
     89 
     90 func (runner *Runner) runConcurrent(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
     91 	// Wait for 1 worker to become available.
     92 	select {
     93 	case <-ctx.Done():
     94 		return nil, nil, nil, ctx.Err()
     95 	case runner.inFlight <- struct{}{}:
     96 		defer func() { <-runner.inFlight }()
     97 	}
     98 
     99 	stdout, stderr := &bytes.Buffer{}, &bytes.Buffer{}
    100 	friendlyErr, err := inv.runWithFriendlyError(ctx, stdout, stderr)
    101 	return stdout, stderr, friendlyErr, err
    102 }
    103 
    104 func (runner *Runner) runPiped(ctx context.Context, inv Invocation, stdout, stderr io.Writer) (error, error) {
    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 nil, ctx.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 := 0; i < maxInFlight; i++ {
    120 		select {
    121 		case <-ctx.Done():
    122 			return nil, ctx.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(ctx, stdout, stderr)
    130 }
    131 
    132 // An Invocation represents a call to the go command.
    133 type 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 string, args ...interface{})
    153 }
    154 
    155 func (i *Invocation) runWithFriendlyError(ctx context.Context, stdout, stderr io.Writer) (friendlyError error, rawError error) {
    156 	rawError = i.run(ctx, stdout, stderr)
    157 	if rawError != nil {
    158 		friendlyError = rawError
    159 		// Check for 'go' executable not being found.
    160 		if ee, ok := 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", friendlyError, stderr)
    167 	}
    168 	return
    169 }
    170 
    171 func (i *Invocation) run(ctx context.Context, stdout, stderr io.Writer) error {
    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(goArgs, i.Args...)
    198 	case "mod":
    199 		// mod needs the sub-verb before flags.
    200 		goArgs = append(goArgs, i.Args[0])
    201 		appendModFile()
    202 		goArgs = append(goArgs, i.Args[1:]...)
    203 	case "get":
    204 		goArgs = append(goArgs, i.BuildFlags...)
    205 		appendModFile()
    206 		goArgs = append(goArgs, i.Args...)
    207 
    208 	default: // notably list and build.
    209 		goArgs = append(goArgs, i.BuildFlags...)
    210 		appendModFile()
    211 		appendModFlag()
    212 		appendOverlayFlag()
    213 		goArgs = append(goArgs, i.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.Env, i.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(ctx, cmd)
    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.
    241 var DebugHangingGoCommands = false
    242 
    243 // runCmdContext is like exec.CommandContext except it sends os.Interrupt
    244 // before os.Kill.
    245 func runCmdContext(ctx context.Context, cmd *exec.Cmd) error {
    246 	if err := cmd.Start(); err != nil {
    247 		return err
    248 	}
    249 	resChan := make(chan error, 1)
    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 
    300 func 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 
    305 The gopls test runner has detected a hanging go command. In order to debug
    306 this, the output of ps and lsof/fstat is printed below.
    307 
    308 See 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", listFiles, err))
    331 		}
    332 	}
    333 	panic(fmt.Sprintf("detected hanging go command (pid %d): see golang/go#54461 for more details", proc.Pid))
    334 }
    335 
    336 func cmdDebugStr(cmd *exec.Cmd) string {
    337 	env := make(map[string]string)
    338 	for _, kv := range cmd.Env {
    339 		split := strings.SplitN(kv, "=", 2)
    340 		if len(split) == 2 {
    341 			k, v := 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(args, quoted)
    351 		} else {
    352 			args = append(args, arg)
    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 }