mirror of
https://github.com/golang/go
synced 2024-11-05 11:36:10 -07:00
internal/lsp/cache: add concurrency error check for go cmds
This change attempts to fix a concurrency error that would cause textDocument/CodeLens, textDocument/Formatting, textDocument/DocumentLink, and textDocument/Hover from failing on go.mod files. The issue was that the go command would return a potential concurrency error since the ModHandle and the ModTidyHandle are both using the temporary go.mod file. Updates golang/go#37824 Change-Id: I6cd63c1f75817c7308e033aec473966536a2a3bd Reviewed-on: https://go-review.googlesource.com/c/tools/+/224917 Reviewed-by: Heschi Kreinick <heschi@google.com> Run-TryBot: Rebecca Stambler <rstambler@golang.org> TryBot-Result: Gobot Gobot <gobot@golang.org>
This commit is contained in:
parent
4d14fc9c00
commit
46bd65c853
@ -19,8 +19,7 @@ import (
|
|||||||
|
|
||||||
var debug = false
|
var debug = false
|
||||||
|
|
||||||
// GetSizes returns the sizes used by the underlying driver with the given parameters.
|
func GetSizes(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) {
|
||||||
func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) {
|
|
||||||
// TODO(matloob): Clean this up. This code is mostly a copy of packages.findExternalDriver.
|
// TODO(matloob): Clean this up. This code is mostly a copy of packages.findExternalDriver.
|
||||||
const toolPrefix = "GOPACKAGESDRIVER="
|
const toolPrefix = "GOPACKAGESDRIVER="
|
||||||
tool := ""
|
tool := ""
|
||||||
@ -40,7 +39,7 @@ func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExp
|
|||||||
}
|
}
|
||||||
|
|
||||||
if tool == "off" {
|
if tool == "off" {
|
||||||
return GetSizesGolist(ctx, buildFlags, env, dir, usesExportData)
|
return GetSizesGolist(ctx, buildFlags, env, gocmdRunner, dir)
|
||||||
}
|
}
|
||||||
|
|
||||||
req, err := json.Marshal(struct {
|
req, err := json.Marshal(struct {
|
||||||
@ -76,7 +75,7 @@ func GetSizes(ctx context.Context, buildFlags, env []string, dir string, usesExp
|
|||||||
return response.Sizes, nil
|
return response.Sizes, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, usesExportData bool) (types.Sizes, error) {
|
func GetSizesGolist(ctx context.Context, buildFlags, env []string, gocmdRunner *gocommand.Runner, dir string) (types.Sizes, error) {
|
||||||
inv := gocommand.Invocation{
|
inv := gocommand.Invocation{
|
||||||
Verb: "list",
|
Verb: "list",
|
||||||
Args: []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"},
|
Args: []string{"-f", "{{context.GOARCH}} {{context.Compiler}}", "--", "unsafe"},
|
||||||
@ -84,7 +83,7 @@ func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, u
|
|||||||
BuildFlags: buildFlags,
|
BuildFlags: buildFlags,
|
||||||
WorkingDir: dir,
|
WorkingDir: dir,
|
||||||
}
|
}
|
||||||
stdout, stderr, friendlyErr, rawErr := inv.RunRaw(ctx)
|
stdout, stderr, friendlyErr, rawErr := gocmdRunner.RunRaw(ctx, inv)
|
||||||
var goarch, compiler string
|
var goarch, compiler string
|
||||||
if rawErr != nil {
|
if rawErr != nil {
|
||||||
if strings.Contains(rawErr.Error(), "cannot find main module") {
|
if strings.Contains(rawErr.Error(), "cannot find main module") {
|
||||||
@ -96,7 +95,7 @@ func GetSizesGolist(ctx context.Context, buildFlags, env []string, dir string, u
|
|||||||
Env: env,
|
Env: env,
|
||||||
WorkingDir: dir,
|
WorkingDir: dir,
|
||||||
}
|
}
|
||||||
envout, enverr := inv.Run(ctx)
|
envout, enverr := gocmdRunner.Run(ctx, inv)
|
||||||
if enverr != nil {
|
if enverr != nil {
|
||||||
return nil, enverr
|
return nil, enverr
|
||||||
}
|
}
|
||||||
|
@ -143,7 +143,7 @@ func goListDriver(cfg *Config, patterns ...string) (*driverResponse, error) {
|
|||||||
sizeswg.Add(1)
|
sizeswg.Add(1)
|
||||||
go func() {
|
go func() {
|
||||||
var sizes types.Sizes
|
var sizes types.Sizes
|
||||||
sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, cfg.BuildFlags, cfg.Env, cfg.Dir, usesExportData(cfg))
|
sizes, sizeserr = packagesdriver.GetSizesGolist(ctx, cfg.BuildFlags, cfg.Env, cfg.gocmdRunner, cfg.Dir)
|
||||||
// types.SizesFor always returns nil or a *types.StdSizes.
|
// types.SizesFor always returns nil or a *types.StdSizes.
|
||||||
response.dr.Sizes, _ = sizes.(*types.StdSizes)
|
response.dr.Sizes, _ = sizes.(*types.StdSizes)
|
||||||
sizeswg.Done()
|
sizeswg.Done()
|
||||||
@ -717,7 +717,7 @@ func golistargs(cfg *Config, words []string) []string {
|
|||||||
func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) {
|
func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer, error) {
|
||||||
cfg := state.cfg
|
cfg := state.cfg
|
||||||
|
|
||||||
inv := &gocommand.Invocation{
|
inv := gocommand.Invocation{
|
||||||
Verb: verb,
|
Verb: verb,
|
||||||
Args: args,
|
Args: args,
|
||||||
BuildFlags: cfg.BuildFlags,
|
BuildFlags: cfg.BuildFlags,
|
||||||
@ -725,8 +725,11 @@ func (state *golistState) invokeGo(verb string, args ...string) (*bytes.Buffer,
|
|||||||
Logf: cfg.Logf,
|
Logf: cfg.Logf,
|
||||||
WorkingDir: cfg.Dir,
|
WorkingDir: cfg.Dir,
|
||||||
}
|
}
|
||||||
|
gocmdRunner := cfg.gocmdRunner
|
||||||
stdout, stderr, _, err := inv.RunRaw(cfg.Context)
|
if gocmdRunner == nil {
|
||||||
|
gocmdRunner = &gocommand.Runner{}
|
||||||
|
}
|
||||||
|
stdout, stderr, _, err := gocmdRunner.RunRaw(cfg.Context, inv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
// Check for 'go' executable not being found.
|
// Check for 'go' executable not being found.
|
||||||
if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
|
if ee, ok := err.(*exec.Error); ok && ee.Err == exec.ErrNotFound {
|
||||||
|
@ -23,6 +23,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
|
|
||||||
"golang.org/x/tools/go/gcexportdata"
|
"golang.org/x/tools/go/gcexportdata"
|
||||||
|
"golang.org/x/tools/internal/gocommand"
|
||||||
"golang.org/x/tools/internal/packagesinternal"
|
"golang.org/x/tools/internal/packagesinternal"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -127,6 +128,9 @@ type Config struct {
|
|||||||
//
|
//
|
||||||
Env []string
|
Env []string
|
||||||
|
|
||||||
|
// gocmdRunner guards go command calls from concurrency errors.
|
||||||
|
gocmdRunner *gocommand.Runner
|
||||||
|
|
||||||
// BuildFlags is a list of command-line flags to be passed through to
|
// BuildFlags is a list of command-line flags to be passed through to
|
||||||
// the build system's query tool.
|
// the build system's query tool.
|
||||||
BuildFlags []string
|
BuildFlags []string
|
||||||
@ -311,6 +315,12 @@ func init() {
|
|||||||
packagesinternal.GetModule = func(p interface{}) *packagesinternal.Module {
|
packagesinternal.GetModule = func(p interface{}) *packagesinternal.Module {
|
||||||
return p.(*Package).module
|
return p.(*Package).module
|
||||||
}
|
}
|
||||||
|
packagesinternal.GetGoCmdRunner = func(config interface{}) *gocommand.Runner {
|
||||||
|
return config.(*Config).gocmdRunner
|
||||||
|
}
|
||||||
|
packagesinternal.SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) {
|
||||||
|
config.(*Config).gocmdRunner = runner
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// An Error describes a problem with a package's metadata, syntax, or types.
|
// An Error describes a problem with a package's metadata, syntax, or types.
|
||||||
@ -473,6 +483,9 @@ func newLoader(cfg *Config) *loader {
|
|||||||
if ld.Config.Env == nil {
|
if ld.Config.Env == nil {
|
||||||
ld.Config.Env = os.Environ()
|
ld.Config.Env = os.Environ()
|
||||||
}
|
}
|
||||||
|
if ld.Config.gocmdRunner == nil {
|
||||||
|
ld.Config.gocmdRunner = &gocommand.Runner{}
|
||||||
|
}
|
||||||
if ld.Context == nil {
|
if ld.Context == nil {
|
||||||
ld.Context = context.Background()
|
ld.Context = context.Background()
|
||||||
}
|
}
|
||||||
|
@ -16,6 +16,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/tools/internal/gocommand"
|
"golang.org/x/tools/internal/gocommand"
|
||||||
|
"golang.org/x/tools/internal/packagesinternal"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Modules is the exporter that produces module layouts.
|
// Modules is the exporter that produces module layouts.
|
||||||
@ -166,6 +167,8 @@ func (modules) Finalize(exported *Exported) error {
|
|||||||
"GOPROXY="+proxyDirToURL(proxyDir),
|
"GOPROXY="+proxyDirToURL(proxyDir),
|
||||||
"GOSUMDB=off",
|
"GOSUMDB=off",
|
||||||
)
|
)
|
||||||
|
gocmdRunner := &gocommand.Runner{}
|
||||||
|
packagesinternal.SetGoCmdRunner(exported.Config, gocmdRunner)
|
||||||
|
|
||||||
// Run go mod download to recreate the mod cache dir with all the extra
|
// Run go mod download to recreate the mod cache dir with all the extra
|
||||||
// stuff in cache. All the files created by Export should be recreated.
|
// stuff in cache. All the files created by Export should be recreated.
|
||||||
@ -176,7 +179,7 @@ func (modules) Finalize(exported *Exported) error {
|
|||||||
BuildFlags: exported.Config.BuildFlags,
|
BuildFlags: exported.Config.BuildFlags,
|
||||||
WorkingDir: exported.Config.Dir,
|
WorkingDir: exported.Config.Dir,
|
||||||
}
|
}
|
||||||
if _, err := inv.Run(context.Background()); err != nil {
|
if _, err := gocmdRunner.Run(context.Background(), inv); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
|
@ -12,10 +12,70 @@ import (
|
|||||||
"io"
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/telemetry/event"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// An Runner will run go command invocations and serialize
|
||||||
|
// them if it sees a concurrency error.
|
||||||
|
type Runner struct {
|
||||||
|
// LoadMu guards packages.Load calls and associated state.
|
||||||
|
loadMu sync.Mutex
|
||||||
|
serializeLoads int
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 calls Runner.RunRaw, serializing requests if they fight over
|
||||||
|
// go.mod changes.
|
||||||
|
func (runner *Runner) Run(ctx context.Context, inv Invocation) (*bytes.Buffer, error) {
|
||||||
|
stdout, _, friendly, _ := runner.RunRaw(ctx, inv)
|
||||||
|
return stdout, friendly
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run calls Innvocation.RunRaw, serializing requests if they fight over
|
||||||
|
// go.mod changes.
|
||||||
|
func (runner *Runner) RunRaw(ctx context.Context, inv Invocation) (*bytes.Buffer, *bytes.Buffer, error, error) {
|
||||||
|
// We want to run invocations concurrently as much as possible. However,
|
||||||
|
// if go.mod updates are needed, only one can make them and the others will
|
||||||
|
// fail. We need to retry in those cases, but we don't want to thrash so
|
||||||
|
// badly we never recover. To avoid that, once we've seen one concurrency
|
||||||
|
// error, start serializing everything until the backlog has cleared out.
|
||||||
|
runner.loadMu.Lock()
|
||||||
|
var locked bool // If true, we hold the mutex and have incremented.
|
||||||
|
if runner.serializeLoads == 0 {
|
||||||
|
runner.loadMu.Unlock()
|
||||||
|
} else {
|
||||||
|
locked = true
|
||||||
|
runner.serializeLoads++
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if locked {
|
||||||
|
runner.serializeLoads--
|
||||||
|
runner.loadMu.Unlock()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
for {
|
||||||
|
stdout, stderr, friendlyErr, err := inv.runRaw(ctx)
|
||||||
|
if friendlyErr == nil || !modConcurrencyError.MatchString(friendlyErr.Error()) {
|
||||||
|
return stdout, stderr, friendlyErr, err
|
||||||
|
}
|
||||||
|
event.Error(ctx, "Load concurrency error, will retry serially", err)
|
||||||
|
if !locked {
|
||||||
|
runner.loadMu.Lock()
|
||||||
|
runner.serializeLoads++
|
||||||
|
locked = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// An Invocation represents a call to the go command.
|
// An Invocation represents a call to the go command.
|
||||||
type Invocation struct {
|
type Invocation struct {
|
||||||
Verb string
|
Verb string
|
||||||
@ -26,16 +86,9 @@ type Invocation struct {
|
|||||||
Logf func(format string, args ...interface{})
|
Logf func(format string, args ...interface{})
|
||||||
}
|
}
|
||||||
|
|
||||||
// Run runs the invocation, returning its stdout and an error suitable for
|
|
||||||
// human consumption, including stderr.
|
|
||||||
func (i *Invocation) Run(ctx context.Context) (*bytes.Buffer, error) {
|
|
||||||
stdout, _, friendly, _ := i.RunRaw(ctx)
|
|
||||||
return stdout, friendly
|
|
||||||
}
|
|
||||||
|
|
||||||
// RunRaw is like RunPiped, but also returns the raw stderr and error for callers
|
// RunRaw is like RunPiped, but also returns the raw stderr and error for callers
|
||||||
// that want to do low-level error handling/recovery.
|
// that want to do low-level error handling/recovery.
|
||||||
func (i *Invocation) RunRaw(ctx context.Context) (stdout *bytes.Buffer, stderr *bytes.Buffer, friendlyError error, rawError error) {
|
func (i *Invocation) runRaw(ctx context.Context) (stdout *bytes.Buffer, stderr *bytes.Buffer, friendlyError error, rawError error) {
|
||||||
stdout = &bytes.Buffer{}
|
stdout = &bytes.Buffer{}
|
||||||
stderr = &bytes.Buffer{}
|
stderr = &bytes.Buffer{}
|
||||||
rawError = i.RunPiped(ctx, stdout, stderr)
|
rawError = i.RunPiped(ctx, stdout, stderr)
|
||||||
|
@ -15,7 +15,8 @@ func TestGoVersion(t *testing.T) {
|
|||||||
inv := gocommand.Invocation{
|
inv := gocommand.Invocation{
|
||||||
Verb: "version",
|
Verb: "version",
|
||||||
}
|
}
|
||||||
if _, err := inv.Run(context.Background()); err != nil {
|
gocmdRunner := &gocommand.Runner{}
|
||||||
|
if _, err := gocmdRunner.Run(context.Background(), inv); err != nil {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -747,6 +747,8 @@ func getPackageExports(ctx context.Context, wrapped func(PackageExport), searchP
|
|||||||
type ProcessEnv struct {
|
type ProcessEnv struct {
|
||||||
LocalPrefix string
|
LocalPrefix string
|
||||||
|
|
||||||
|
GocmdRunner *gocommand.Runner
|
||||||
|
|
||||||
BuildFlags []string
|
BuildFlags []string
|
||||||
|
|
||||||
// If non-empty, these will be used instead of the
|
// If non-empty, these will be used instead of the
|
||||||
@ -830,7 +832,7 @@ func (e *ProcessEnv) invokeGo(ctx context.Context, verb string, args ...string)
|
|||||||
Logf: e.Logf,
|
Logf: e.Logf,
|
||||||
WorkingDir: e.WorkingDir,
|
WorkingDir: e.WorkingDir,
|
||||||
}
|
}
|
||||||
return inv.Run(ctx)
|
return e.GocmdRunner.Run(ctx, inv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func addStdlibCandidates(pass *pass, refs references) {
|
func addStdlibCandidates(pass *pass, refs references) {
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/tools/go/packages/packagestest"
|
"golang.org/x/tools/go/packages/packagestest"
|
||||||
|
"golang.org/x/tools/internal/gocommand"
|
||||||
)
|
)
|
||||||
|
|
||||||
var testDebug = flag.Bool("debug", false, "enable debug output")
|
var testDebug = flag.Bool("debug", false, "enable debug output")
|
||||||
@ -1638,6 +1639,7 @@ func (c testConfig) test(t *testing.T, fn func(*goimportTest)) {
|
|||||||
GO111MODULE: env["GO111MODULE"],
|
GO111MODULE: env["GO111MODULE"],
|
||||||
GOSUMDB: env["GOSUMDB"],
|
GOSUMDB: env["GOSUMDB"],
|
||||||
WorkingDir: exported.Config.Dir,
|
WorkingDir: exported.Config.Dir,
|
||||||
|
GocmdRunner: &gocommand.Runner{},
|
||||||
},
|
},
|
||||||
exported: exported,
|
exported: exported,
|
||||||
}
|
}
|
||||||
|
@ -27,6 +27,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"golang.org/x/tools/go/ast/astutil"
|
"golang.org/x/tools/go/ast/astutil"
|
||||||
|
"golang.org/x/tools/internal/gocommand"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Options is golang.org/x/tools/imports.Options with extra internal-only options.
|
// Options is golang.org/x/tools/imports.Options with extra internal-only options.
|
||||||
@ -154,6 +155,10 @@ func initialize(filename string, src []byte, opt *Options) ([]byte, *Options, er
|
|||||||
GOSUMDB: os.Getenv("GOSUMDB"),
|
GOSUMDB: os.Getenv("GOSUMDB"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Set the gocmdRunner if the user has not provided it.
|
||||||
|
if opt.Env.GocmdRunner == nil {
|
||||||
|
opt.Env.GocmdRunner = &gocommand.Runner{}
|
||||||
|
}
|
||||||
if src == nil {
|
if src == nil {
|
||||||
b, err := ioutil.ReadFile(filename)
|
b, err := ioutil.ReadFile(filename)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -5,6 +5,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/gocommand"
|
||||||
"golang.org/x/tools/internal/testenv"
|
"golang.org/x/tools/internal/testenv"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -31,8 +32,9 @@ func TestNilOpts(t *testing.T) {
|
|||||||
name: "default",
|
name: "default",
|
||||||
opt: &Options{
|
opt: &Options{
|
||||||
Env: &ProcessEnv{
|
Env: &ProcessEnv{
|
||||||
GOPATH: build.Default.GOPATH,
|
GOPATH: build.Default.GOPATH,
|
||||||
GOROOT: build.Default.GOROOT,
|
GOROOT: build.Default.GOROOT,
|
||||||
|
GocmdRunner: &gocommand.Runner{},
|
||||||
},
|
},
|
||||||
Comments: true,
|
Comments: true,
|
||||||
TabIndent: true,
|
TabIndent: true,
|
||||||
|
@ -19,6 +19,7 @@ import (
|
|||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"golang.org/x/mod/module"
|
"golang.org/x/mod/module"
|
||||||
|
"golang.org/x/tools/internal/gocommand"
|
||||||
"golang.org/x/tools/internal/gopathwalk"
|
"golang.org/x/tools/internal/gopathwalk"
|
||||||
"golang.org/x/tools/internal/testenv"
|
"golang.org/x/tools/internal/testenv"
|
||||||
"golang.org/x/tools/txtar"
|
"golang.org/x/tools/txtar"
|
||||||
@ -676,6 +677,7 @@ func setup(t *testing.T, main, wd string) *modTest {
|
|||||||
GOPROXY: proxyDirToURL(proxyDir),
|
GOPROXY: proxyDirToURL(proxyDir),
|
||||||
GOSUMDB: "off",
|
GOSUMDB: "off",
|
||||||
WorkingDir: filepath.Join(mainDir, wd),
|
WorkingDir: filepath.Join(mainDir, wd),
|
||||||
|
GocmdRunner: &gocommand.Runner{},
|
||||||
}
|
}
|
||||||
if *testDebug {
|
if *testDebug {
|
||||||
env.Logf = log.Printf
|
env.Logf = log.Printf
|
||||||
@ -850,6 +852,7 @@ func TestInvalidModCache(t *testing.T) {
|
|||||||
GOPATH: filepath.Join(dir, "gopath"),
|
GOPATH: filepath.Join(dir, "gopath"),
|
||||||
GO111MODULE: "on",
|
GO111MODULE: "on",
|
||||||
GOSUMDB: "off",
|
GOSUMDB: "off",
|
||||||
|
GocmdRunner: &gocommand.Runner{},
|
||||||
WorkingDir: dir,
|
WorkingDir: dir,
|
||||||
}
|
}
|
||||||
resolver := newModuleResolver(env)
|
resolver := newModuleResolver(env)
|
||||||
@ -918,9 +921,10 @@ import _ "rsc.io/quote"
|
|||||||
|
|
||||||
func BenchmarkScanModCache(b *testing.B) {
|
func BenchmarkScanModCache(b *testing.B) {
|
||||||
env := &ProcessEnv{
|
env := &ProcessEnv{
|
||||||
GOPATH: build.Default.GOPATH,
|
GOPATH: build.Default.GOPATH,
|
||||||
GOROOT: build.Default.GOROOT,
|
GOROOT: build.Default.GOROOT,
|
||||||
Logf: log.Printf,
|
GocmdRunner: &gocommand.Runner{},
|
||||||
|
Logf: log.Printf,
|
||||||
}
|
}
|
||||||
exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT}
|
exclude := []gopathwalk.RootType{gopathwalk.RootGOROOT}
|
||||||
scanToSlice(env.GetResolver(), exclude)
|
scanToSlice(env.GetResolver(), exclude)
|
||||||
|
2
internal/lsp/cache/load.go
vendored
2
internal/lsp/cache/load.go
vendored
@ -84,7 +84,7 @@ func (s *snapshot) load(ctx context.Context, scopes ...interface{}) error {
|
|||||||
defer done()
|
defer done()
|
||||||
|
|
||||||
cfg := s.Config(ctx)
|
cfg := s.Config(ctx)
|
||||||
pkgs, err := s.view.loadPackages(cfg, query...)
|
pkgs, err := packages.Load(cfg, query...)
|
||||||
|
|
||||||
// If the context was canceled, return early. Otherwise, we might be
|
// If the context was canceled, return early. Otherwise, we might be
|
||||||
// type-checking an incomplete result. Check the context directly,
|
// type-checking an incomplete result. Check the context directly,
|
||||||
|
15
internal/lsp/cache/mod.go
vendored
15
internal/lsp/cache/mod.go
vendored
@ -20,6 +20,7 @@ import (
|
|||||||
"golang.org/x/tools/internal/lsp/protocol"
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
"golang.org/x/tools/internal/lsp/source"
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
"golang.org/x/tools/internal/memoize"
|
"golang.org/x/tools/internal/memoize"
|
||||||
|
"golang.org/x/tools/internal/packagesinternal"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
"golang.org/x/tools/internal/telemetry/event"
|
"golang.org/x/tools/internal/telemetry/event"
|
||||||
errors "golang.org/x/xerrors"
|
errors "golang.org/x/xerrors"
|
||||||
@ -220,7 +221,7 @@ func goModWhy(ctx context.Context, cfg *packages.Config, folder string, data *mo
|
|||||||
for _, req := range data.origParsedFile.Require {
|
for _, req := range data.origParsedFile.Require {
|
||||||
inv.Args = append(inv.Args, req.Mod.Path)
|
inv.Args = append(inv.Args, req.Mod.Path)
|
||||||
}
|
}
|
||||||
stdout, err := inv.Run(ctx)
|
stdout, err := packagesinternal.GetGoCmdRunner(cfg).Run(ctx, inv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -247,7 +248,7 @@ func dependencyUpgrades(ctx context.Context, cfg *packages.Config, folder string
|
|||||||
Env: cfg.Env,
|
Env: cfg.Env,
|
||||||
WorkingDir: folder,
|
WorkingDir: folder,
|
||||||
}
|
}
|
||||||
stdout, err := inv.Run(ctx)
|
stdout, err := packagesinternal.GetGoCmdRunner(cfg).Run(ctx, inv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -288,6 +289,7 @@ func (s *snapshot) ModTidyHandle(ctx context.Context, realfh source.FileHandle)
|
|||||||
cfg := s.Config(ctx)
|
cfg := s.Config(ctx)
|
||||||
options := s.View().Options()
|
options := s.View().Options()
|
||||||
folder := s.View().Folder().Filename()
|
folder := s.View().Folder().Filename()
|
||||||
|
gocmdRunner := s.view.gocmdRunner
|
||||||
|
|
||||||
wsPackages, err := s.WorkspacePackages(ctx)
|
wsPackages, err := s.WorkspacePackages(ctx)
|
||||||
if ctx.Err() != nil {
|
if ctx.Err() != nil {
|
||||||
@ -358,12 +360,9 @@ func (s *snapshot) ModTidyHandle(ctx context.Context, realfh source.FileHandle)
|
|||||||
Env: cfg.Env,
|
Env: cfg.Env,
|
||||||
WorkingDir: folder,
|
WorkingDir: folder,
|
||||||
}
|
}
|
||||||
if _, err := inv.Run(ctx); err != nil {
|
if _, err := gocmdRunner.Run(ctx, inv); err != nil {
|
||||||
// Ignore concurrency errors here.
|
return &modData{
|
||||||
if !modConcurrencyError.MatchString(err.Error()) {
|
err: err,
|
||||||
return &modData{
|
|
||||||
err: err,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
internal/lsp/cache/session.go
vendored
2
internal/lsp/cache/session.go
vendored
@ -11,6 +11,7 @@ import (
|
|||||||
"sync"
|
"sync"
|
||||||
"sync/atomic"
|
"sync/atomic"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/gocommand"
|
||||||
"golang.org/x/tools/internal/lsp/debug"
|
"golang.org/x/tools/internal/lsp/debug"
|
||||||
"golang.org/x/tools/internal/lsp/source"
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
@ -133,6 +134,7 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
|
|||||||
modHandles: make(map[span.URI]*modHandle),
|
modHandles: make(map[span.URI]*modHandle),
|
||||||
},
|
},
|
||||||
ignoredURIs: make(map[span.URI]struct{}),
|
ignoredURIs: make(map[span.URI]struct{}),
|
||||||
|
gocmdRunner: &gocommand.Runner{},
|
||||||
}
|
}
|
||||||
v.snapshot.view = v
|
v.snapshot.view = v
|
||||||
|
|
||||||
|
6
internal/lsp/cache/snapshot.go
vendored
6
internal/lsp/cache/snapshot.go
vendored
@ -19,6 +19,7 @@ import (
|
|||||||
"golang.org/x/tools/go/packages"
|
"golang.org/x/tools/go/packages"
|
||||||
"golang.org/x/tools/internal/lsp/debug/tag"
|
"golang.org/x/tools/internal/lsp/debug/tag"
|
||||||
"golang.org/x/tools/internal/lsp/source"
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
|
"golang.org/x/tools/internal/packagesinternal"
|
||||||
"golang.org/x/tools/internal/span"
|
"golang.org/x/tools/internal/span"
|
||||||
"golang.org/x/tools/internal/telemetry/event"
|
"golang.org/x/tools/internal/telemetry/event"
|
||||||
errors "golang.org/x/xerrors"
|
errors "golang.org/x/xerrors"
|
||||||
@ -94,7 +95,7 @@ func (s *snapshot) Config(ctx context.Context) *packages.Config {
|
|||||||
verboseOutput := s.view.options.VerboseOutput
|
verboseOutput := s.view.options.VerboseOutput
|
||||||
s.view.optionsMu.Unlock()
|
s.view.optionsMu.Unlock()
|
||||||
|
|
||||||
return &packages.Config{
|
cfg := &packages.Config{
|
||||||
Env: env,
|
Env: env,
|
||||||
Dir: s.view.folder.Filename(),
|
Dir: s.view.folder.Filename(),
|
||||||
Context: ctx,
|
Context: ctx,
|
||||||
@ -117,6 +118,9 @@ func (s *snapshot) Config(ctx context.Context) *packages.Config {
|
|||||||
},
|
},
|
||||||
Tests: true,
|
Tests: true,
|
||||||
}
|
}
|
||||||
|
packagesinternal.SetGoCmdRunner(cfg, s.view.gocmdRunner)
|
||||||
|
|
||||||
|
return cfg
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *snapshot) buildOverlay() map[string][]byte {
|
func (s *snapshot) buildOverlay() map[string][]byte {
|
||||||
|
58
internal/lsp/cache/view.go
vendored
58
internal/lsp/cache/view.go
vendored
@ -15,12 +15,10 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"golang.org/x/tools/go/packages"
|
|
||||||
"golang.org/x/tools/internal/gocommand"
|
"golang.org/x/tools/internal/gocommand"
|
||||||
"golang.org/x/tools/internal/imports"
|
"golang.org/x/tools/internal/imports"
|
||||||
"golang.org/x/tools/internal/lsp/debug"
|
"golang.org/x/tools/internal/lsp/debug"
|
||||||
@ -111,9 +109,8 @@ type view struct {
|
|||||||
// `go env` variables that need to be tracked.
|
// `go env` variables that need to be tracked.
|
||||||
gopath, gocache string
|
gopath, gocache string
|
||||||
|
|
||||||
// LoadMu guards packages.Load calls and associated state.
|
// gocmdRunner guards go command calls from concurrency errors.
|
||||||
loadMu sync.Mutex
|
gocmdRunner *gocommand.Runner
|
||||||
serializeLoads int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type builtinPackageHandle struct {
|
type builtinPackageHandle struct {
|
||||||
@ -279,7 +276,7 @@ func (v *view) WriteEnv(ctx context.Context, w io.Writer) error {
|
|||||||
Env: env,
|
Env: env,
|
||||||
WorkingDir: v.Folder().Filename(),
|
WorkingDir: v.Folder().Filename(),
|
||||||
}
|
}
|
||||||
stdout, err := inv.Run(ctx)
|
stdout, err := v.gocmdRunner.Run(ctx, inv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -364,6 +361,7 @@ func (v *view) buildProcessEnv(ctx context.Context) (*imports.ProcessEnv, error)
|
|||||||
WorkingDir: v.folder.Filename(),
|
WorkingDir: v.folder.Filename(),
|
||||||
BuildFlags: buildFlags,
|
BuildFlags: buildFlags,
|
||||||
LocalPrefix: localPrefix,
|
LocalPrefix: localPrefix,
|
||||||
|
GocmdRunner: v.gocmdRunner,
|
||||||
}
|
}
|
||||||
if verboseOutput {
|
if verboseOutput {
|
||||||
processEnv.Logf = func(format string, args ...interface{}) {
|
processEnv.Logf = func(format string, args ...interface{}) {
|
||||||
@ -734,7 +732,7 @@ func (v *view) getGoEnv(ctx context.Context, env []string) (string, error) {
|
|||||||
Env: env,
|
Env: env,
|
||||||
WorkingDir: v.Folder().Filename(),
|
WorkingDir: v.Folder().Filename(),
|
||||||
}
|
}
|
||||||
stdout, err := inv.Run(ctx)
|
stdout, err := v.gocmdRunner.Run(ctx, inv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
@ -767,50 +765,6 @@ func (v *view) getGoEnv(ctx context.Context, env []string) (string, error) {
|
|||||||
return "", nil
|
return "", nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// 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`)
|
|
||||||
|
|
||||||
// LoadPackages calls packages.Load, serializing requests if they fight over
|
|
||||||
// go.mod changes.
|
|
||||||
func (v *view) loadPackages(cfg *packages.Config, patterns ...string) ([]*packages.Package, error) {
|
|
||||||
// We want to run go list calls concurrently as much as possible. However,
|
|
||||||
// if go.mod updates are needed, only one can make them and the others will
|
|
||||||
// fail. We need to retry in those cases, but we don't want to thrash so
|
|
||||||
// badly we never recover. To avoid that, once we've seen one concurrency
|
|
||||||
// error, start serializing everything until the backlog has cleared out.
|
|
||||||
// This could all be avoided on 1.14 by using multiple -modfiles.
|
|
||||||
|
|
||||||
v.loadMu.Lock()
|
|
||||||
var locked bool // If true, we hold the mutex and have incremented.
|
|
||||||
if v.serializeLoads == 0 {
|
|
||||||
v.loadMu.Unlock()
|
|
||||||
} else {
|
|
||||||
locked = true
|
|
||||||
v.serializeLoads++
|
|
||||||
}
|
|
||||||
defer func() {
|
|
||||||
if locked {
|
|
||||||
v.serializeLoads--
|
|
||||||
v.loadMu.Unlock()
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
|
||||||
pkgs, err := packages.Load(cfg, patterns...)
|
|
||||||
if err == nil || !modConcurrencyError.MatchString(err.Error()) {
|
|
||||||
return pkgs, err
|
|
||||||
}
|
|
||||||
|
|
||||||
event.Error(cfg.Context, "Load concurrency error, will retry serially", err)
|
|
||||||
if !locked {
|
|
||||||
v.loadMu.Lock()
|
|
||||||
v.serializeLoads++
|
|
||||||
locked = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This function will return the main go.mod file for this folder if it exists and whether the -modfile
|
// This function will return the main go.mod file for this folder if it exists and whether the -modfile
|
||||||
// flag exists for this version of go.
|
// flag exists for this version of go.
|
||||||
func (v *view) modfileFlagExists(ctx context.Context, env []string) (bool, error) {
|
func (v *view) modfileFlagExists(ctx context.Context, env []string) (bool, error) {
|
||||||
@ -824,7 +778,7 @@ func (v *view) modfileFlagExists(ctx context.Context, env []string) (bool, error
|
|||||||
Env: append(env, "GO111MODULE=off"),
|
Env: append(env, "GO111MODULE=off"),
|
||||||
WorkingDir: v.Folder().Filename(),
|
WorkingDir: v.Folder().Filename(),
|
||||||
}
|
}
|
||||||
stdout, err := inv.Run(ctx)
|
stdout, err := v.gocmdRunner.Run(ctx, inv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, err
|
return false, err
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ import (
|
|||||||
"golang.org/x/tools/internal/gocommand"
|
"golang.org/x/tools/internal/gocommand"
|
||||||
"golang.org/x/tools/internal/lsp/protocol"
|
"golang.org/x/tools/internal/lsp/protocol"
|
||||||
"golang.org/x/tools/internal/lsp/source"
|
"golang.org/x/tools/internal/lsp/source"
|
||||||
|
"golang.org/x/tools/internal/packagesinternal"
|
||||||
"golang.org/x/tools/internal/xcontext"
|
"golang.org/x/tools/internal/xcontext"
|
||||||
errors "golang.org/x/xerrors"
|
errors "golang.org/x/xerrors"
|
||||||
)
|
)
|
||||||
@ -32,14 +33,16 @@ func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCom
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
cfg := snapshot.Config(ctx)
|
||||||
// Run go.mod tidy on the view.
|
// Run go.mod tidy on the view.
|
||||||
inv := gocommand.Invocation{
|
inv := gocommand.Invocation{
|
||||||
Verb: "mod",
|
Verb: "mod",
|
||||||
Args: []string{"tidy"},
|
Args: []string{"tidy"},
|
||||||
Env: snapshot.Config(ctx).Env,
|
Env: cfg.Env,
|
||||||
WorkingDir: snapshot.View().Folder().Filename(),
|
WorkingDir: snapshot.View().Folder().Filename(),
|
||||||
}
|
}
|
||||||
if _, err := inv.Run(ctx); err != nil {
|
gocmdRunner := packagesinternal.GetGoCmdRunner(cfg)
|
||||||
|
if _, err := gocmdRunner.Run(ctx, inv); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
case "upgrade.dependency":
|
case "upgrade.dependency":
|
||||||
@ -52,14 +55,16 @@ func (s *Server) executeCommand(ctx context.Context, params *protocol.ExecuteCom
|
|||||||
if !ok {
|
if !ok {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
cfg := snapshot.Config(ctx)
|
||||||
// Run "go get" on the dependency to upgrade it to the latest version.
|
// Run "go get" on the dependency to upgrade it to the latest version.
|
||||||
inv := gocommand.Invocation{
|
inv := gocommand.Invocation{
|
||||||
Verb: "get",
|
Verb: "get",
|
||||||
Args: strings.Split(deps, " "),
|
Args: strings.Split(deps, " "),
|
||||||
Env: snapshot.Config(ctx).Env,
|
Env: cfg.Env,
|
||||||
WorkingDir: snapshot.View().Folder().Filename(),
|
WorkingDir: snapshot.View().Folder().Filename(),
|
||||||
}
|
}
|
||||||
if _, err := inv.Run(ctx); err != nil {
|
gocmdRunner := packagesinternal.GetGoCmdRunner(cfg)
|
||||||
|
if _, err := gocmdRunner.Run(ctx, inv); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,10 +50,10 @@ func PrintVersionInfo(ctx context.Context, w io.Writer, verbose bool, mode Print
|
|||||||
})
|
})
|
||||||
fmt.Fprint(w, "\n")
|
fmt.Fprint(w, "\n")
|
||||||
section(w, mode, "Go info", func() {
|
section(w, mode, "Go info", func() {
|
||||||
i := &gocommand.Invocation{
|
gocmdRunner := &gocommand.Runner{}
|
||||||
|
version, err := gocmdRunner.Run(ctx, gocommand.Invocation{
|
||||||
Verb: "version",
|
Verb: "version",
|
||||||
}
|
})
|
||||||
version, err := i.Run(ctx)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(err)
|
panic(err)
|
||||||
}
|
}
|
||||||
|
@ -157,7 +157,8 @@ func (w *Workspace) RunGoCommand(ctx context.Context, verb string, args ...strin
|
|||||||
Args: args,
|
Args: args,
|
||||||
WorkingDir: w.workdir,
|
WorkingDir: w.workdir,
|
||||||
}
|
}
|
||||||
_, stderr, _, err := inv.RunRaw(ctx)
|
gocmdRunner := &gocommand.Runner{}
|
||||||
|
_, stderr, _, err := gocmdRunner.RunRaw(ctx, inv)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,11 @@
|
|||||||
// Package packagesinternal exposes internal-only fields from go/packages.
|
// Package packagesinternal exposes internal-only fields from go/packages.
|
||||||
package packagesinternal
|
package packagesinternal
|
||||||
|
|
||||||
import "time"
|
import (
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"golang.org/x/tools/internal/gocommand"
|
||||||
|
)
|
||||||
|
|
||||||
// Fields must match go list;
|
// Fields must match go list;
|
||||||
type Module struct {
|
type Module struct {
|
||||||
@ -25,3 +29,7 @@ type ModuleError struct {
|
|||||||
var GetForTest = func(p interface{}) string { return "" }
|
var GetForTest = func(p interface{}) string { return "" }
|
||||||
|
|
||||||
var GetModule = func(p interface{}) *Module { return nil }
|
var GetModule = func(p interface{}) *Module { return nil }
|
||||||
|
|
||||||
|
var GetGoCmdRunner = func(config interface{}) *gocommand.Runner { return nil }
|
||||||
|
|
||||||
|
var SetGoCmdRunner = func(config interface{}, runner *gocommand.Runner) {}
|
||||||
|
Loading…
Reference in New Issue
Block a user