Skip to content

Commit 43adb69

Browse files
committed
internal/lsp: disable network access for some go commands
For users with poor network connections, go command invocations that access the network may hang up for long periods of time. Even for users with good network connections, accessing proxy.golang.org or github can take a few seconds, which is a long time to lock up an editor. Disable network access via GOPROXY=off when running most go commands. There are two notable exceptions. First, the initial workspace load is allowed, though subsequent loads are not. My reasoning is that if the user is opening a project they've just downloaded, they probably expect to fetch its dependencies. (Also, it's convenient to keep the regtests going.) The second is the go mod tidy diagnostics, which I hope to address in a later change. All the other commands that access the network are at the user's request. When the workspace load fails due to a dependency that hasn't been downloaded, we present a quick fix on the go.mod line to do so. The only way that happens is if there's a manual modification to the go.mod file, since all of the other quick fixes will do the download. So I don't think there's a need to present the fix anywhere else. Updates golang/go#38462. Change-Id: I470bc1ba790db7c1afee54d0b28fa0c6fd203fb9 Reviewed-on: https://go-review.googlesource.com/c/tools/+/274120 Trust: Heschi Kreinick <[email protected]> Run-TryBot: Heschi Kreinick <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Go Bot <[email protected]> Reviewed-by: Rebecca Stambler <[email protected]> Reviewed-by: Robert Findley <[email protected]>
1 parent 9bc6a97 commit 43adb69

File tree

10 files changed

+103
-42
lines changed

10 files changed

+103
-42
lines changed

gopls/internal/regtest/modfile_test.go

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -444,18 +444,14 @@ func main() {
444444
env.RegexpReplace("go.mod", "v1.2.2", "v1.2.3")
445445
env.Editor.SaveBuffer(env.Ctx, "go.mod") // go.mod changes must be on disk
446446

447-
// Bring the go.sum file back into sync. Multi-module workspace
448-
// mode currently doesn't set -mod=readonly, so won't need to.
449-
if !strings.Contains(t.Name(), "experimental") {
450-
d := protocol.PublishDiagnosticsParams{}
451-
env.Await(
452-
OnceMet(
453-
env.DiagnosticAtRegexp("go.mod", "module"),
454-
ReadDiagnostics("go.mod", &d),
455-
),
456-
)
457-
env.ApplyQuickFixes("go.mod", d.Diagnostics)
458-
}
447+
d := protocol.PublishDiagnosticsParams{}
448+
env.Await(
449+
OnceMet(
450+
env.DiagnosticAtRegexpWithMessage("go.mod", "example.com v1.2.3", "[email protected]"),
451+
ReadDiagnostics("go.mod", &d),
452+
),
453+
)
454+
env.ApplyQuickFixes("go.mod", d.Diagnostics)
459455

460456
env.Await(
461457
EmptyDiagnostics("go.mod"),

gopls/internal/regtest/workspace_test.go

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import (
1414

1515
"golang.org/x/tools/internal/lsp"
1616
"golang.org/x/tools/internal/lsp/fake"
17+
"golang.org/x/tools/internal/lsp/protocol"
1718
"golang.org/x/tools/internal/testenv"
1819
)
1920

@@ -272,6 +273,18 @@ func Hello() int {
272273
env.Await(
273274
CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 2),
274275
)
276+
if testenv.Go1Point() < 14 {
277+
// On 1.14 and above, the go mod tidy diagnostics accidentally
278+
// download for us. This is the behavior we actually want.
279+
d := protocol.PublishDiagnosticsParams{}
280+
env.Await(
281+
OnceMet(
282+
env.DiagnosticAtRegexpWithMessage("moda/a/go.mod", "require b.com v1.2.3", "[email protected]"),
283+
ReadDiagnostics("moda/a/go.mod", &d),
284+
),
285+
)
286+
env.ApplyQuickFixes("moda/a/go.mod", d.Diagnostics)
287+
}
275288
got, _ := env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
276289
if want := "[email protected]/b/b.go"; !strings.HasSuffix(got, want) {
277290
t.Errorf("expected %s, got %v", want, got)
@@ -448,16 +461,20 @@ require (
448461
replace a.com => %s/moda/a
449462
replace b.com => %s/modb
450463
`, workdir, workdir))
464+
env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1))
465+
// Check that go.mod diagnostics picked up the newly active mod file.
466+
// The local version of modb has an extra dependency we need to download.
467+
env.OpenFile("modb/go.mod")
468+
var d protocol.PublishDiagnosticsParams
451469
env.Await(
452470
OnceMet(
453-
CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChangeWatchedFiles), 1),
454-
env.DiagnosticAtRegexp("modb/b/b.go", "x"),
471+
env.DiagnosticAtRegexp("modb/go.mod", `require example.com v1.2.3`),
472+
ReadDiagnostics("modb/go.mod", &d),
455473
),
456474
)
457-
env.OpenFile("modb/go.mod")
458-
// Check that go.mod diagnostics picked up the newly active mod file.
459-
env.Await(env.DiagnosticAtRegexp("modb/go.mod", `require example.com v1.2.3`))
460-
// ...and that jumping to definition now goes to b.com in the workspace.
475+
env.ApplyQuickFixes("modb/go.mod", d.Diagnostics)
476+
env.Await(env.DiagnosticAtRegexp("modb/b/b.go", "x"))
477+
// Jumping to definition should now go to b.com in the workspace.
461478
location, _ = env.GoToDefinition("moda/a/a.go", env.RegexpSearch("moda/a/a.go", "Hello"))
462479
if want := "modb/b/b.go"; !strings.HasSuffix(location, want) {
463480
t.Errorf("expected %s, got %v", want, location)
@@ -474,9 +491,8 @@ require (
474491
475492
replace a.com => %s/moda/a
476493
`, workdir))
477-
env.Await(CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChange), 1))
478494
env.Await(OnceMet(
479-
CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChange), 1),
495+
CompletedWork(lsp.DiagnosticWorkTitle(lsp.FromDidChange), 2),
480496
EmptyDiagnostics("modb/go.mod"),
481497
))
482498

internal/lsp/cache/load.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ type metadata struct {
4747

4848
// load calls packages.Load for the given scopes, updating package metadata,
4949
// import graph, and mapped files with the result.
50-
func (s *snapshot) load(ctx context.Context, scopes ...interface{}) error {
50+
func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...interface{}) error {
5151
var query []string
5252
var containsDir bool // for logging
5353
for _, scope := range scopes {
@@ -94,7 +94,11 @@ func (s *snapshot) load(ctx context.Context, scopes ...interface{}) error {
9494
ctx, done := event.Start(ctx, "cache.view.load", tag.Query.Of(query))
9595
defer done()
9696

97-
_, inv, cleanup, err := s.goCommandInvocation(ctx, source.LoadWorkspace, &gocommand.Invocation{
97+
flags := source.LoadWorkspace
98+
if allowNetwork {
99+
flags |= source.AllowNetwork
100+
}
101+
_, inv, cleanup, err := s.goCommandInvocation(ctx, flags, &gocommand.Invocation{
98102
WorkingDir: s.view.rootURI.Filename(),
99103
})
100104
if err != nil {

internal/lsp/cache/mod.go

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -290,7 +290,7 @@ func (s *snapshot) ModUpgrade(ctx context.Context, fh source.FileHandle) (map[st
290290
// (see golang/go#38711).
291291
inv.ModFlag = "readonly"
292292
}
293-
stdout, err := snapshot.RunGoCommandDirect(ctx, source.Normal, inv)
293+
stdout, err := snapshot.RunGoCommandDirect(ctx, source.Normal|source.AllowNetwork, inv)
294294
if err != nil {
295295
return &modUpgradeData{err: err}
296296
}
@@ -387,6 +387,26 @@ func (s *snapshot) extractGoCommandError(ctx context.Context, snapshot source.Sn
387387
if err != nil {
388388
return nil
389389
}
390+
if v.Path != "" && strings.Contains(goCmdError, "disabled by GOPROXY=off") {
391+
args, err := source.MarshalArgs(fh.URI(), false, []string{fmt.Sprintf("%v@%v", v.Path, v.Version)})
392+
if err != nil {
393+
return nil
394+
}
395+
return &source.Error{
396+
Message: fmt.Sprintf("%v@%v has not been downloaded", v.Path, v.Version),
397+
Kind: source.ListError,
398+
Range: rng,
399+
URI: fh.URI(),
400+
SuggestedFixes: []source.SuggestedFix{{
401+
Title: fmt.Sprintf("Download %v@%v", v.Path, v.Version),
402+
Command: &protocol.Command{
403+
Title: source.CommandAddDependency.Title,
404+
Command: source.CommandAddDependency.ID(),
405+
Arguments: args,
406+
},
407+
}},
408+
}
409+
}
390410
return &source.Error{
391411
Message: goCmdError,
392412
Range: rng,

internal/lsp/cache/mod_tidy.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ func (s *snapshot) ModTidy(ctx context.Context, pm *source.ParsedModule) (*sourc
105105
Args: []string{"tidy"},
106106
WorkingDir: filepath.Dir(fh.URI().Filename()),
107107
}
108-
tmpURI, inv, cleanup, err := snapshot.goCommandInvocation(ctx, source.WriteTemporaryModFile, inv)
108+
tmpURI, inv, cleanup, err := snapshot.goCommandInvocation(ctx, source.WriteTemporaryModFile|source.AllowNetwork, inv)
109109
if err != nil {
110110
return &modTidyData{err: err}
111111
}

internal/lsp/cache/snapshot.go

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -228,7 +228,7 @@ func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packa
228228
return cfg
229229
}
230230

231-
func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.InvocationMode, inv *gocommand.Invocation) (*bytes.Buffer, error) {
231+
func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error) {
232232
_, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
233233
if err != nil {
234234
return nil, err
@@ -238,7 +238,7 @@ func (s *snapshot) RunGoCommandDirect(ctx context.Context, mode source.Invocatio
238238
return s.view.session.gocmdRunner.Run(ctx, *inv)
239239
}
240240

241-
func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.InvocationMode, inv *gocommand.Invocation, stdout, stderr io.Writer) error {
241+
func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error {
242242
_, inv, cleanup, err := s.goCommandInvocation(ctx, mode, inv)
243243
if err != nil {
244244
return err
@@ -247,7 +247,7 @@ func (s *snapshot) RunGoCommandPiped(ctx context.Context, mode source.Invocation
247247
return s.view.session.gocmdRunner.RunPiped(ctx, *inv, stdout, stderr)
248248
}
249249

250-
func (s *snapshot) goCommandInvocation(ctx context.Context, mode source.InvocationMode, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) {
250+
func (s *snapshot) goCommandInvocation(ctx context.Context, flags source.InvocationFlags, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) {
251251
s.view.optionsMu.Lock()
252252
inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.go111module)
253253
inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...)
@@ -259,6 +259,11 @@ func (s *snapshot) goCommandInvocation(ctx context.Context, mode source.Invocati
259259
return "", inv, cleanup, nil
260260
}
261261

262+
mode, allowNetwork := flags.Mode(), flags.AllowNetwork()
263+
if !allowNetwork {
264+
inv.Env = append(inv.Env, "GOPROXY=off")
265+
}
266+
262267
var modURI span.URI
263268
// Select the module context to use.
264269
// If we're type checking, we need to use the workspace context, meaning
@@ -453,7 +458,7 @@ func (s *snapshot) packageHandlesForFile(ctx context.Context, uri span.URI, mode
453458
// calls to packages.Load. Determine what we should do instead.
454459
}
455460
if reload {
456-
if err := s.load(ctx, fileURI(uri)); err != nil {
461+
if err := s.load(ctx, false, fileURI(uri)); err != nil {
457462
return nil, err
458463
}
459464
}
@@ -1008,7 +1013,7 @@ func (s *snapshot) reloadWorkspace(ctx context.Context) error {
10081013
// If the view's build configuration is invalid, we cannot reload by
10091014
// package path. Just reload the directory instead.
10101015
if missingMetadata && !s.ValidBuildConfiguration() {
1011-
return s.load(ctx, viewLoadScope("LOAD_INVALID_VIEW"))
1016+
return s.load(ctx, false, viewLoadScope("LOAD_INVALID_VIEW"))
10121017
}
10131018

10141019
if len(pkgPathSet) == 0 {
@@ -1019,7 +1024,7 @@ func (s *snapshot) reloadWorkspace(ctx context.Context) error {
10191024
for pkgPath := range pkgPathSet {
10201025
pkgPaths = append(pkgPaths, pkgPath)
10211026
}
1022-
return s.load(ctx, pkgPaths...)
1027+
return s.load(ctx, false, pkgPaths...)
10231028
}
10241029

10251030
func (s *snapshot) reloadOrphanedFiles(ctx context.Context) error {
@@ -1032,7 +1037,7 @@ func (s *snapshot) reloadOrphanedFiles(ctx context.Context) error {
10321037
return nil
10331038
}
10341039

1035-
err := s.load(ctx, scopes...)
1040+
err := s.load(ctx, false, scopes...)
10361041

10371042
// If we failed to load some files, i.e. they have no metadata,
10381043
// mark the failures so we don't bother retrying until the file's

internal/lsp/cache/view.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -548,7 +548,7 @@ func (s *snapshot) initialize(ctx context.Context, firstAttempt bool) {
548548
if len(scopes) == 0 {
549549
scopes = append(scopes, viewLoadScope("LOAD_VIEW"))
550550
}
551-
err := s.load(ctx, append(scopes, packagePath("builtin"))...)
551+
err := s.load(ctx, firstAttempt, append(scopes, packagePath("builtin"))...)
552552
if ctx.Err() != nil {
553553
return
554554
}

internal/lsp/command.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -203,7 +203,7 @@ func (s *Server) runCommand(ctx context.Context, work *workDone, command *source
203203
if command == source.CommandVendor {
204204
action = "vendor"
205205
}
206-
return runSimpleGoCommand(ctx, snapshot, source.UpdateUserModFile, uri.SpanURI(), "mod", []string{action})
206+
return runSimpleGoCommand(ctx, snapshot, source.UpdateUserModFile|source.AllowNetwork, uri.SpanURI(), "mod", []string{action})
207207
case source.CommandUpdateGoSum:
208208
var uri protocol.DocumentURI
209209
if err := source.UnmarshalArgs(args, &uri); err != nil {
@@ -214,7 +214,7 @@ func (s *Server) runCommand(ctx context.Context, work *workDone, command *source
214214
if !ok {
215215
return err
216216
}
217-
return runSimpleGoCommand(ctx, snapshot, source.UpdateUserModFile, uri.SpanURI(), "list", []string{"all"})
217+
return runSimpleGoCommand(ctx, snapshot, source.UpdateUserModFile|source.AllowNetwork, uri.SpanURI(), "list", []string{"all"})
218218
case source.CommandAddDependency, source.CommandUpgradeDependency, source.CommandRemoveDependency:
219219
var uri protocol.DocumentURI
220220
var goCmdArgs []string
@@ -397,10 +397,10 @@ func (s *Server) runGoGetModule(ctx context.Context, snapshot source.Snapshot, u
397397
return err
398398
}
399399
}
400-
return runSimpleGoCommand(ctx, snapshot, source.UpdateUserModFile, uri, "get", append([]string{"-d"}, args...))
400+
return runSimpleGoCommand(ctx, snapshot, source.UpdateUserModFile|source.AllowNetwork, uri, "get", append([]string{"-d"}, args...))
401401
}
402402

403-
func runSimpleGoCommand(ctx context.Context, snapshot source.Snapshot, mode source.InvocationMode, uri span.URI, verb string, args []string) error {
403+
func runSimpleGoCommand(ctx context.Context, snapshot source.Snapshot, mode source.InvocationFlags, uri span.URI, verb string, args []string) error {
404404
_, err := snapshot.RunGoCommandDirect(ctx, mode, &gocommand.Invocation{
405405
Verb: verb,
406406
Args: args,

internal/lsp/mod/diagnostics.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import (
1313
"golang.org/x/tools/internal/lsp/debug/tag"
1414
"golang.org/x/tools/internal/lsp/protocol"
1515
"golang.org/x/tools/internal/lsp/source"
16+
errors "golang.org/x/xerrors"
1617
)
1718

1819
func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.VersionedFileIdentity][]*source.Diagnostic, error) {
@@ -36,7 +37,7 @@ func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.Vers
3637
Range: e.Range,
3738
Source: e.Category,
3839
}
39-
if e.Category == "syntax" {
40+
if e.Category == "syntax" || e.Kind == source.ListError {
4041
d.Severity = protocol.SeverityError
4142
} else {
4243
d.Severity = protocol.SeverityWarning
@@ -65,6 +66,12 @@ func ErrorsForMod(ctx context.Context, snapshot source.Snapshot, fh source.FileH
6566
return nil, nil
6667
}
6768
if err != nil {
69+
// Some error messages can also be displayed as diagnostics.
70+
if criticalErr := (*source.CriticalError)(nil); errors.As(err, &criticalErr) {
71+
return criticalErr.ErrorList, nil
72+
} else if srcErrList := (source.ErrorList)(nil); errors.As(err, &srcErrList) {
73+
return srcErrList, nil
74+
}
6875
return nil, err
6976
}
7077
return tidied.Errors, nil

internal/lsp/source/view.go

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -84,11 +84,11 @@ type Snapshot interface {
8484

8585
// RunGoCommandPiped runs the given `go` command, writing its output
8686
// to stdout and stderr. Verb, Args, and WorkingDir must be specified.
87-
RunGoCommandPiped(ctx context.Context, mode InvocationMode, inv *gocommand.Invocation, stdout, stderr io.Writer) error
87+
RunGoCommandPiped(ctx context.Context, mode InvocationFlags, inv *gocommand.Invocation, stdout, stderr io.Writer) error
8888

8989
// RunGoCommandDirect runs the given `go` command. Verb, Args, and
9090
// WorkingDir must be specified.
91-
RunGoCommandDirect(ctx context.Context, mode InvocationMode, inv *gocommand.Invocation) (*bytes.Buffer, error)
91+
RunGoCommandDirect(ctx context.Context, mode InvocationFlags, inv *gocommand.Invocation) (*bytes.Buffer, error)
9292

9393
// RunProcessEnvFunc runs fn with the process env for this snapshot's view.
9494
// Note: the process env contains cached module and filesystem state.
@@ -161,13 +161,14 @@ const (
161161
WidestPackage
162162
)
163163

164-
// InvocationMode represents the goal of a particular go command invocation.
165-
type InvocationMode int
164+
// InvocationFlags represents the settings of a particular go command invocation.
165+
// It is a mode, plus a set of flag bits.
166+
type InvocationFlags int
166167

167168
const (
168169
// Normal is appropriate for commands that might be run by a user and don't
169170
// deliberately modify go.mod files, e.g. `go test`.
170-
Normal InvocationMode = iota
171+
Normal InvocationFlags = iota
171172
// UpdateUserModFile is for commands that intend to update the user's real
172173
// go.mod file, e.g. `go mod tidy` in response to a user's request to tidy.
173174
UpdateUserModFile
@@ -178,8 +179,20 @@ const (
178179
// LoadWorkspace is for packages.Load, and other operations that should
179180
// consider the whole workspace at once.
180181
LoadWorkspace
182+
183+
// AllowNetwork is a flag bit that indicates the invocation should be
184+
// allowed to access the network.
185+
AllowNetwork = 1 << 10
181186
)
182187

188+
func (m InvocationFlags) Mode() InvocationFlags {
189+
return m & (AllowNetwork - 1)
190+
}
191+
192+
func (m InvocationFlags) AllowNetwork() bool {
193+
return m&AllowNetwork != 0
194+
}
195+
183196
// View represents a single workspace.
184197
// This is the level at which we maintain configuration like working directory
185198
// and build tags.

0 commit comments

Comments
 (0)