Skip to content

Commit 0a9721c

Browse files
committed
gopls/internal/lsp: move options into the snapshot
Snapshots should be idempotent, and the fact that configuration changes do not cause snapshots to increment has been a long-standing source of bugs. After a fair bit of setup, this CL finally moves options onto the snapshot. The only remaining use of the options stored on the View is for "minorOptionsChange" detection. This is of questionable value, but I opted not to delete it in this CL (Chesterton's fence). There is still more cleanup to do (and tests to update), but that is deferred to later CLs. Fixes golang/go#61325 Fixes golang/go#42814 Change-Id: If82dc199af13ddaa3464b2a67a8bab2013161f26 Reviewed-on: https://go-review.googlesource.com/c/tools/+/526159 LUCI-TryBot-Result: Go LUCI <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent fb4bd11 commit 0a9721c

17 files changed

+145
-136
lines changed

gopls/internal/lsp/cache/analysis.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ func (snapshot *snapshot) Analyze(ctx context.Context, pkgs map[PackageID]unit,
192192
toSrc := make(map[*analysis.Analyzer]*source.Analyzer)
193193
var enabled []*analysis.Analyzer // enabled subset + transitive requirements
194194
for _, a := range analyzers {
195-
if a.IsEnabled(snapshot.view.Options()) {
195+
if a.IsEnabled(snapshot.options) {
196196
toSrc[a.Analyzer] = a
197197
enabled = append(enabled, a.Analyzer)
198198
}
@@ -309,7 +309,7 @@ func (snapshot *snapshot) Analyze(ctx context.Context, pkgs map[PackageID]unit,
309309
// Now that we have read all files,
310310
// we no longer need the snapshot.
311311
// (but options are needed for progress reporting)
312-
options := snapshot.view.Options()
312+
options := snapshot.options
313313
snapshot = nil
314314

315315
// Progress reporting. If supported, gopls reports progress on analysis

gopls/internal/lsp/cache/check.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1345,8 +1345,8 @@ func (s *snapshot) typeCheckInputs(ctx context.Context, m *source.Metadata) (typ
13451345
depsByImpPath: m.DepsByImpPath,
13461346
goVersion: goVersion,
13471347

1348-
relatedInformation: s.view.Options().RelatedInformationSupported,
1349-
linkTarget: s.view.Options().LinkTarget,
1348+
relatedInformation: s.options.RelatedInformationSupported,
1349+
linkTarget: s.options.LinkTarget,
13501350
moduleMode: s.view.moduleMode(),
13511351
}, nil
13521352
}

gopls/internal/lsp/cache/imports.go

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -54,15 +54,13 @@ func (s *importsState) runProcessEnvFunc(ctx context.Context, snapshot *snapshot
5454

5555
// view.goEnv is immutable -- changes make a new view. Options can change.
5656
// We can't compare build flags directly because we may add -modfile.
57-
snapshot.view.optionsMu.Lock()
58-
localPrefix := snapshot.view.options.Local
59-
currentBuildFlags := snapshot.view.options.BuildFlags
60-
currentDirectoryFilters := snapshot.view.options.DirectoryFilters
57+
localPrefix := snapshot.options.Local
58+
currentBuildFlags := snapshot.options.BuildFlags
59+
currentDirectoryFilters := snapshot.options.DirectoryFilters
6160
changed := !reflect.DeepEqual(currentBuildFlags, s.cachedBuildFlags) ||
62-
snapshot.view.options.VerboseOutput != (s.processEnv.Logf != nil) ||
61+
snapshot.options.VerboseOutput != (s.processEnv.Logf != nil) ||
6362
modFileHash != s.cachedModFileHash ||
64-
!reflect.DeepEqual(snapshot.view.options.DirectoryFilters, s.cachedDirectoryFilters)
65-
snapshot.view.optionsMu.Unlock()
63+
!reflect.DeepEqual(snapshot.options.DirectoryFilters, s.cachedDirectoryFilters)
6664

6765
// If anything relevant to imports has changed, clear caches and
6866
// update the processEnv. Clearing caches blocks on any background
@@ -120,7 +118,7 @@ func populateProcessEnvFromSnapshot(ctx context.Context, pe *imports.ProcessEnv,
120118
ctx, done := event.Start(ctx, "cache.populateProcessEnvFromSnapshot")
121119
defer done()
122120

123-
if snapshot.view.Options().VerboseOutput {
121+
if snapshot.options.VerboseOutput {
124122
pe.Logf = func(format string, args ...interface{}) {
125123
event.Log(ctx, fmt.Sprintf(format, args...))
126124
}

gopls/internal/lsp/cache/load.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadSc
7575
if err != nil {
7676
continue
7777
}
78-
if isStandaloneFile(contents, s.view.Options().StandaloneTags) {
78+
if isStandaloneFile(contents, s.options.StandaloneTags) {
7979
standalone = true
8080
query = append(query, uri.Filename())
8181
} else {
@@ -160,7 +160,7 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadSc
160160
}
161161

162162
moduleErrs := make(map[string][]packages.Error) // module path -> errors
163-
filterFunc := s.view.filterFunc()
163+
filterFunc := s.filterFunc()
164164
newMetadata := make(map[PackageID]*source.Metadata)
165165
for _, pkg := range pkgs {
166166
// The Go command returns synthetic list results for module queries that
@@ -178,7 +178,7 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadSc
178178
continue
179179
}
180180

181-
if !containsDir || s.view.Options().VerboseOutput {
181+
if !containsDir || s.options.VerboseOutput {
182182
event.Log(ctx, eventName, append(
183183
source.SnapshotLabels(s),
184184
tag.Package.Of(pkg.ID),
@@ -359,7 +359,7 @@ func (s *snapshot) applyCriticalErrorToFiles(ctx context.Context, msg string, fi
359359
for _, fh := range files {
360360
// Place the diagnostics on the package or module declarations.
361361
var rng protocol.Range
362-
switch s.view.FileKind(fh) {
362+
switch s.FileKind(fh) {
363363
case source.Go:
364364
if pgf, err := s.ParseGo(ctx, fh, source.ParseHeader); err == nil {
365365
// Check that we have a valid `package foo` range to use for positioning the error.
@@ -616,7 +616,7 @@ func containsPackageLocked(s *snapshot, m *source.Metadata) bool {
616616
uris[uri] = struct{}{}
617617
}
618618

619-
filterFunc := s.view.filterFunc()
619+
filterFunc := s.filterFunc()
620620
for uri := range uris {
621621
// Don't use view.contains here. go.work files may include modules
622622
// outside of the workspace folder.
@@ -671,7 +671,7 @@ func containsFileInWorkspaceLocked(s *snapshot, m *source.Metadata) bool {
671671

672672
// The package's files are in this view. It may be a workspace package.
673673
// Vendored packages are not likely to be interesting to the user.
674-
if !strings.Contains(string(uri), "/vendor/") && s.view.contains(uri) {
674+
if !strings.Contains(string(uri), "/vendor/") && s.contains(uri) {
675675
return true
676676
}
677677
}

gopls/internal/lsp/cache/session.go

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -117,9 +117,9 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
117117
v := &View{
118118
id: strconv.FormatInt(index, 10),
119119
gocmdRunner: s.gocmdRunner,
120+
lastOptions: options,
120121
initialWorkspaceLoad: make(chan struct{}),
121122
initializationSema: make(chan struct{}, 1),
122-
options: options,
123123
baseCtx: baseCtx,
124124
name: name,
125125
moduleUpgrades: map[span.URI]map[string]string{},
@@ -167,6 +167,7 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
167167
workspaceModFiles: wsModFiles,
168168
workspaceModFilesErr: wsModFilesErr,
169169
pkgIndex: typerefs.NewPackageIndex(),
170+
options: options,
170171
}
171172
// Save one reference in the view.
172173
v.releaseSnapshot = v.snapshot.Acquire()
@@ -255,9 +256,15 @@ func bestViewForURI(uri span.URI, views []*View) *View {
255256
}
256257
// TODO(rfindley): this should consider the workspace layout (i.e.
257258
// go.work).
258-
if view.contains(uri) {
259+
snapshot, release, err := view.getSnapshot()
260+
if err != nil {
261+
// view is shutdown
262+
continue
263+
}
264+
if snapshot.contains(uri) {
259265
longest = view
260266
}
267+
release()
261268
}
262269
if longest != nil {
263270
return longest
@@ -420,7 +427,7 @@ func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModif
420427
// synchronously to change processing? Can we assume that the env did not
421428
// change, and derive go.work using a combination of the configured
422429
// GOWORK value and filesystem?
423-
info, err := s.getWorkspaceInformation(ctx, view.folder, view.Options())
430+
info, err := s.getWorkspaceInformation(ctx, view.folder, view.lastOptions)
424431
if err != nil {
425432
// Catastrophic failure, equivalent to a failure of session
426433
// initialization and therefore should almost never happen. One
@@ -434,7 +441,7 @@ func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModif
434441
}
435442

436443
if info != view.workspaceInformation {
437-
if err := s.updateViewLocked(ctx, view, view.Options()); err != nil {
444+
if err := s.updateViewLocked(ctx, view, view.lastOptions); err != nil {
438445
// More catastrophic failure. The view may or may not still exist.
439446
// The best we can do is log and move on.
440447
event.Error(ctx, "recreating view", err)
@@ -492,7 +499,7 @@ func (s *Session) DidModifyFiles(ctx context.Context, changes []source.FileModif
492499
var releases []func()
493500
viewToSnapshot := map[*View]*snapshot{}
494501
for view, changed := range views {
495-
snapshot, release := view.invalidateContent(ctx, changed, forceReloadMetadata)
502+
snapshot, release := view.invalidateContent(ctx, changed, nil, forceReloadMetadata)
496503
releases = append(releases, release)
497504
viewToSnapshot[view] = snapshot
498505
}

gopls/internal/lsp/cache/snapshot.go

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -187,6 +187,10 @@ type snapshot struct {
187187
// active packages. Running type checking batches in parallel after an
188188
// invalidation can cause redundant calculation of this shared state.
189189
typeCheckMu sync.Mutex
190+
191+
// options holds the user configuration at the time this snapshot was
192+
// created.
193+
options *source.Options
190194
}
191195

192196
var globalSnapshotID uint64
@@ -275,12 +279,39 @@ func (s *snapshot) View() source.View {
275279
return s.view
276280
}
277281

278-
func (s *snapshot) FileKind(h source.FileHandle) source.FileKind {
279-
return s.view.FileKind(h)
282+
func (s *snapshot) FileKind(fh source.FileHandle) source.FileKind {
283+
// The kind of an unsaved buffer comes from the
284+
// TextDocumentItem.LanguageID field in the didChange event,
285+
// not from the file name. They may differ.
286+
if o, ok := fh.(*Overlay); ok {
287+
if o.kind != source.UnknownKind {
288+
return o.kind
289+
}
290+
}
291+
292+
fext := filepath.Ext(fh.URI().Filename())
293+
switch fext {
294+
case ".go":
295+
return source.Go
296+
case ".mod":
297+
return source.Mod
298+
case ".sum":
299+
return source.Sum
300+
case ".work":
301+
return source.Work
302+
}
303+
exts := s.options.TemplateExtensions
304+
for _, ext := range exts {
305+
if fext == ext || fext == "."+ext {
306+
return source.Tmpl
307+
}
308+
}
309+
// and now what? This should never happen, but it does for cgo before go1.15
310+
return source.Go
280311
}
281312

282313
func (s *snapshot) Options() *source.Options {
283-
return s.view.Options() // temporarily return view options.
314+
return s.options // temporarily return view options.
284315
}
285316

286317
func (s *snapshot) BackgroundContext() context.Context {
@@ -306,7 +337,7 @@ func (s *snapshot) Templates() map[span.URI]source.FileHandle {
306337

307338
tmpls := map[span.URI]source.FileHandle{}
308339
s.files.Range(func(k span.URI, fh source.FileHandle) {
309-
if s.view.FileKind(fh) == source.Tmpl {
340+
if s.FileKind(fh) == source.Tmpl {
310341
tmpls[k] = fh
311342
}
312343
})
@@ -354,8 +385,7 @@ func (s *snapshot) workspaceMode() workspaceMode {
354385
return mode
355386
}
356387
mode |= moduleMode
357-
options := s.view.Options()
358-
if options.TempModfile {
388+
if s.options.TempModfile {
359389
mode |= tempModfile
360390
}
361391
return mode
@@ -368,9 +398,6 @@ func (s *snapshot) workspaceMode() workspaceMode {
368398
// multiple modules in on config, so buildOverlay needs to filter overlays by
369399
// module.
370400
func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packages.Config {
371-
s.view.optionsMu.Lock()
372-
verboseOutput := s.view.options.VerboseOutput
373-
s.view.optionsMu.Unlock()
374401

375402
cfg := &packages.Config{
376403
Context: ctx,
@@ -393,7 +420,7 @@ func (s *snapshot) config(ctx context.Context, inv *gocommand.Invocation) *packa
393420
panic("go/packages must not be used to parse files")
394421
},
395422
Logf: func(format string, args ...interface{}) {
396-
if verboseOutput {
423+
if s.options.VerboseOutput {
397424
event.Log(ctx, fmt.Sprintf(format, args...))
398425
}
399426
},
@@ -475,18 +502,16 @@ func (s *snapshot) RunGoCommands(ctx context.Context, allowNetwork bool, wd stri
475502
// it used only after call to tempModFile. Clarify that it is only
476503
// non-nil on success.
477504
func (s *snapshot) goCommandInvocation(ctx context.Context, flags source.InvocationFlags, inv *gocommand.Invocation) (tmpURI span.URI, updatedInv *gocommand.Invocation, cleanup func(), err error) {
478-
s.view.optionsMu.Lock()
479-
allowModfileModificationOption := s.view.options.AllowModfileModifications
480-
allowNetworkOption := s.view.options.AllowImplicitNetworkAccess
505+
allowModfileModificationOption := s.options.AllowModfileModifications
506+
allowNetworkOption := s.options.AllowImplicitNetworkAccess
481507

482508
// TODO(rfindley): this is very hard to follow, and may not even be doing the
483509
// right thing: should inv.Env really trample view.options? Do we ever invoke
484510
// this with a non-empty inv.Env?
485511
//
486512
// We should refactor to make it clearer that the correct env is being used.
487-
inv.Env = append(append(append(os.Environ(), s.view.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.GO111MODULE())
488-
inv.BuildFlags = append([]string{}, s.view.options.BuildFlags...)
489-
s.view.optionsMu.Unlock()
513+
inv.Env = append(append(append(os.Environ(), s.options.EnvSlice()...), inv.Env...), "GO111MODULE="+s.view.GO111MODULE())
514+
inv.BuildFlags = append([]string{}, s.options.BuildFlags...)
490515
cleanup = func() {} // fallback
491516

492517
// All logic below is for module mode.
@@ -993,8 +1018,7 @@ func (s *snapshot) workspaceDirs(ctx context.Context) []string {
9931018
// Code) that do not send notifications for individual files in a directory
9941019
// when the entire directory is deleted.
9951020
func (s *snapshot) watchSubdirs() bool {
996-
opts := s.view.Options()
997-
switch p := opts.SubdirWatchPatterns; p {
1021+
switch p := s.options.SubdirWatchPatterns; p {
9981022
case source.SubdirWatchPatternsOn:
9991023
return true
10001024
case source.SubdirWatchPatternsOff:
@@ -1007,7 +1031,7 @@ func (s *snapshot) watchSubdirs() bool {
10071031
// requirements that client names do not change. We should update the VS
10081032
// Code extension to set a default value of "subdirWatchPatterns" to "on",
10091033
// so that this workaround is only temporary.
1010-
if opts.ClientInfo != nil && opts.ClientInfo.Name == "Visual Studio Code" {
1034+
if s.options.ClientInfo != nil && s.options.ClientInfo.Name == "Visual Studio Code" {
10111035
return true
10121036
}
10131037
return false
@@ -1505,7 +1529,7 @@ func (s *snapshot) reloadOrphanedOpenFiles(ctx context.Context) error {
15051529
var files []*Overlay
15061530
for _, o := range open {
15071531
uri := o.URI()
1508-
if s.IsBuiltin(uri) || s.view.FileKind(o) != source.Go {
1532+
if s.IsBuiltin(uri) || s.FileKind(o) != source.Go {
15091533
continue
15101534
}
15111535
if len(meta.ids[uri]) == 0 {
@@ -1606,7 +1630,7 @@ func (s *snapshot) OrphanedFileDiagnostics(ctx context.Context) (map[span.URI]*s
16061630
searchOverlays:
16071631
for _, o := range s.overlays() {
16081632
uri := o.URI()
1609-
if s.IsBuiltin(uri) || s.view.FileKind(o) != source.Go {
1633+
if s.IsBuiltin(uri) || s.FileKind(o) != source.Go {
16101634
continue
16111635
}
16121636
md, err := s.MetadataForFile(ctx, uri)
@@ -1805,7 +1829,7 @@ func inVendor(uri span.URI) bool {
18051829
return found && strings.Contains(after, "/")
18061830
}
18071831

1808-
func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]source.FileHandle, forceReloadMetadata bool) (*snapshot, func()) {
1832+
func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]source.FileHandle, newOptions *source.Options, forceReloadMetadata bool) (*snapshot, func()) {
18091833
ctx, done := event.Start(ctx, "cache.snapshot.clone")
18101834
defer done()
18111835

@@ -1838,6 +1862,11 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]source
18381862
workspaceModFilesErr: s.workspaceModFilesErr,
18391863
importGraph: s.importGraph,
18401864
pkgIndex: s.pkgIndex,
1865+
options: s.options,
1866+
}
1867+
1868+
if newOptions != nil {
1869+
result.options = newOptions
18411870
}
18421871

18431872
// Create a lease on the new snapshot.

0 commit comments

Comments
 (0)