Skip to content

Commit ff22fab

Browse files
committed
gopls/internal/lsp/cache: eliminate obsolete invalidation step
This change removes the call to invalidatePackagesLocked after buildMetadata in the load method; now that we no longer permit invalid metadata, it is unnecessary. (The sole remaining call to invalidatePackagesLocked was inlined into Clone.) Also: - check the invalidation invariant in load before Clone. - merge MetadataForFile and getOrLoadIDsForURI since all callers want the Metadata (not just PackageID) and the defensive check is no longer needed. - simplify awaitLoadedAllErrors by calling getInitializationError. - reduce allocation and critical sections in various snapshot methods that retrieve the metadataGraph. - flag various places where we can avoid type-checking. - various other doc comments. Updates golang/go#54180 Change-Id: I82dca203b2520259630b2fd9d08e120030d44a96 Reviewed-on: https://go-review.googlesource.com/c/tools/+/452057 Run-TryBot: Alan Donovan <[email protected]> Reviewed-by: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 3881046 commit ff22fab

File tree

15 files changed

+98
-133
lines changed

15 files changed

+98
-133
lines changed

gopls/internal/lsp/cache/load.go

+12-29
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import (
2121
"golang.org/x/tools/gopls/internal/lsp/protocol"
2222
"golang.org/x/tools/gopls/internal/lsp/source"
2323
"golang.org/x/tools/gopls/internal/span"
24+
"golang.org/x/tools/internal/bug"
2425
"golang.org/x/tools/internal/event"
2526
"golang.org/x/tools/internal/event/tag"
2627
"golang.org/x/tools/internal/gocommand"
@@ -213,48 +214,30 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadSc
213214

214215
s.mu.Lock()
215216

216-
// Only update metadata where we don't already have valid metadata.
217-
//
218-
// We want to preserve an invariant that s.packages.Get(id).m.Metadata
219-
// matches s.meta.metadata[id].Metadata. By avoiding overwriting valid
220-
// metadata, we minimize the amount of invalidation required to preserve this
221-
// invariant.
222-
//
223-
// TODO(rfindley): perform a sanity check that metadata matches here. If not,
224-
// we have an invalidation bug elsewhere.
217+
// Compute the minimal metadata updates (for Clone)
218+
// required to preserve this invariant:
219+
// for all id, s.packages.Get(id).m == s.meta.metadata[id].
225220
updates := make(map[PackageID]*source.Metadata)
226-
var updatedIDs []PackageID
227221
for _, m := range newMetadata {
228222
if existing := s.meta.metadata[m.ID]; existing == nil {
229223
updates[m.ID] = m
230-
updatedIDs = append(updatedIDs, m.ID)
231224
delete(s.shouldLoad, m.ID)
232225
}
233226
}
227+
// Assert the invariant.
228+
s.packages.Range(func(k, v interface{}) {
229+
pk, ph := k.(packageKey), v.(*packageHandle)
230+
if s.meta.metadata[pk.id] != ph.m {
231+
// TODO(adonovan): upgrade to unconditional panic after Jan 2023.
232+
bug.Reportf("inconsistent metadata")
233+
}
234+
})
234235

235236
event.Log(ctx, fmt.Sprintf("%s: updating metadata for %d packages", eventName, len(updates)))
236237

237-
// Invalidate the reverse transitive closure of packages that have changed.
238-
//
239-
// Note that the original metadata is being invalidated here, so we use the
240-
// original metadata graph to compute the reverse closure.
241-
invalidatedPackages := s.meta.reverseTransitiveClosure(updatedIDs...)
242-
243238
s.meta = s.meta.Clone(updates)
244239
s.resetIsActivePackageLocked()
245240

246-
// Invalidate any packages and analysis results we may have associated with
247-
// this metadata.
248-
//
249-
// Generally speaking we should have already invalidated these results in
250-
// snapshot.clone, but with experimentalUseInvalidMetadata is may be possible
251-
// that we have re-computed stale results before the reload completes. In
252-
// this case, we must re-invalidate here.
253-
//
254-
// TODO(golang/go#54180): if we decide to make experimentalUseInvalidMetadata
255-
// obsolete, we should avoid this invalidation.
256-
s.invalidatePackagesLocked(invalidatedPackages)
257-
258241
s.workspacePackages = computeWorkspacePackagesLocked(s, s.meta)
259242
s.dumpWorkspace("load")
260243
s.mu.Unlock()

gopls/internal/lsp/cache/pkg.go

+2-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type (
2525
ImportPath = source.ImportPath
2626
)
2727

28-
// pkg contains the type information needed by the source package.
28+
// pkg contains parse trees and type information for a package.
2929
type pkg struct {
3030
m *source.Metadata
3131
mode source.ParseMode
@@ -34,7 +34,7 @@ type pkg struct {
3434
compiledGoFiles []*source.ParsedGoFile
3535
diagnostics []*source.Diagnostic
3636
deps map[PackageID]*pkg // use m.DepsBy{Pkg,Imp}Path to look up ID
37-
version *module.Version
37+
version *module.Version // may be nil; may differ from m.Module.Version
3838
parseErrors []scanner.ErrorList
3939
typeErrors []types.Error
4040
types *types.Package

gopls/internal/lsp/cache/snapshot.go

+57-87
Original file line numberDiff line numberDiff line change
@@ -100,10 +100,10 @@ type snapshot struct {
100100
// It may be invalidated when a file's content changes.
101101
//
102102
// Invariants to preserve:
103-
// - packages.Get(id).m.Metadata == meta.metadata[id].Metadata for all ids
103+
// - packages.Get(id).meta == meta.metadata[id] for all ids
104104
// - if a package is in packages, then all of its dependencies should also
105105
// be in packages, unless there is a missing import
106-
packages *persistent.Map // from packageKey to *memoize.Promise[*packageHandle]
106+
packages *persistent.Map // from packageKey to *packageHandle
107107

108108
// isActivePackageCache maps package ID to the cached value if it is active or not.
109109
// It may be invalidated when metadata changes or a new file is opened or closed.
@@ -655,6 +655,8 @@ func (s *snapshot) PackagesForFile(ctx context.Context, uri span.URI, mode sourc
655655
func (s *snapshot) PackageForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, pkgPolicy source.PackageFilter) (source.Package, error) {
656656
ctx = event.Label(ctx, tag.URI.Of(uri))
657657

658+
// TODO(adonovan): opt: apply pkgPolicy to the list of
659+
// Metadatas before initiating loading of any package.
658660
phs, err := s.packageHandlesForFile(ctx, uri, mode, false)
659661
if err != nil {
660662
return nil, err
@@ -699,24 +701,24 @@ func (s *snapshot) packageHandlesForFile(ctx context.Context, uri span.URI, mode
699701
if kind := s.view.FileKind(fh); kind != source.Go {
700702
return nil, fmt.Errorf("no packages for non-Go file %s (%v)", uri, kind)
701703
}
702-
knownIDs, err := s.getOrLoadIDsForURI(ctx, uri)
704+
metas, err := s.MetadataForFile(ctx, uri)
703705
if err != nil {
704706
return nil, err
705707
}
706708

707709
var phs []*packageHandle
708-
for _, id := range knownIDs {
710+
for _, m := range metas {
709711
// Filter out any intermediate test variants. We typically aren't
710712
// interested in these packages for file= style queries.
711-
if m := s.getMetadata(id); m != nil && m.IsIntermediateTestVariant() && !withIntermediateTestVariants {
713+
if m.IsIntermediateTestVariant() && !withIntermediateTestVariants {
712714
continue
713715
}
714716
parseMode := source.ParseFull
715717
if mode == source.TypecheckWorkspace {
716-
parseMode = s.workspaceParseMode(id)
718+
parseMode = s.workspaceParseMode(m.ID)
717719
}
718720

719-
ph, err := s.buildPackageHandle(ctx, id, parseMode)
721+
ph, err := s.buildPackageHandle(ctx, m.ID, parseMode)
720722
if err != nil {
721723
return nil, err
722724
}
@@ -725,12 +727,10 @@ func (s *snapshot) packageHandlesForFile(ctx context.Context, uri span.URI, mode
725727
return phs, nil
726728
}
727729

728-
// getOrLoadIDsForURI returns package IDs associated with the file uri. If no
729-
// such packages exist or if they are known to be stale, it reloads the file.
730-
//
731-
// If experimentalUseInvalidMetadata is set, this function may return package
732-
// IDs with invalid metadata.
733-
func (s *snapshot) getOrLoadIDsForURI(ctx context.Context, uri span.URI) ([]PackageID, error) {
730+
// MetadataForFile returns the Metadata for each package associated
731+
// with the file uri. If no such package exists or if they are known
732+
// to be stale, it reloads metadata for the file.
733+
func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]*source.Metadata, error) {
734734
s.mu.Lock()
735735

736736
// Start with the set of package associations derived from the last load.
@@ -772,23 +772,31 @@ func (s *snapshot) getOrLoadIDsForURI(ctx context.Context, uri span.URI) ([]Pack
772772
s.clearShouldLoad(scope)
773773

774774
// Don't return an error here, as we may still return stale IDs.
775-
// Furthermore, the result of getOrLoadIDsForURI should be consistent upon
775+
// Furthermore, the result of MetadataForFile should be consistent upon
776776
// subsequent calls, even if the file is marked as unloadable.
777777
if err != nil && !errors.Is(err, errNoPackages) {
778-
event.Error(ctx, "getOrLoadIDsForURI", err)
778+
event.Error(ctx, "MetadataForFile", err)
779779
}
780780
}
781781

782+
// Retrieve the metadata.
782783
s.mu.Lock()
784+
defer s.mu.Unlock()
783785
ids = s.meta.ids[uri]
784-
// metadata is only ever added by loading, so if we get here and still have
785-
// no ids, uri is unloadable.
786+
metas := make([]*source.Metadata, len(ids))
787+
for i, id := range ids {
788+
metas[i] = s.meta.metadata[id]
789+
if metas[i] == nil {
790+
panic("nil metadata")
791+
}
792+
}
793+
// Metadata is only ever added by loading,
794+
// so if we get here and still have
795+
// no IDs, uri is unloadable.
786796
if !unloadable && len(ids) == 0 {
787797
s.unloadableFiles[uri] = struct{}{}
788798
}
789-
s.mu.Unlock()
790-
791-
return ids, nil
799+
return metas, nil
792800
}
793801

794802
func (s *snapshot) GetReverseDependencies(ctx context.Context, id PackageID) ([]source.Package, error) {
@@ -1135,37 +1143,17 @@ func (s *snapshot) Symbols(ctx context.Context) map[span.URI][]source.Symbol {
11351143
return result
11361144
}
11371145

1138-
func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]*source.Metadata, error) {
1139-
knownIDs, err := s.getOrLoadIDsForURI(ctx, uri)
1140-
if err != nil {
1141-
return nil, err
1142-
}
1143-
var mds []*source.Metadata
1144-
for _, id := range knownIDs {
1145-
md := s.getMetadata(id)
1146-
// TODO(rfindley): knownIDs and metadata should be in sync, but existing
1147-
// code is defensive of nil metadata.
1148-
if md != nil {
1149-
mds = append(mds, md)
1150-
}
1151-
}
1152-
return mds, nil
1153-
}
1154-
11551146
func (s *snapshot) KnownPackages(ctx context.Context) ([]source.Package, error) {
11561147
if err := s.awaitLoaded(ctx); err != nil {
11571148
return nil, err
11581149
}
11591150

1160-
ids := make([]source.PackageID, 0, len(s.meta.metadata))
11611151
s.mu.Lock()
1162-
for id := range s.meta.metadata {
1163-
ids = append(ids, id)
1164-
}
1152+
g := s.meta
11651153
s.mu.Unlock()
11661154

1167-
pkgs := make([]source.Package, 0, len(ids))
1168-
for _, id := range ids {
1155+
pkgs := make([]source.Package, 0, len(g.metadata))
1156+
for id := range g.metadata {
11691157
pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id))
11701158
if err != nil {
11711159
return nil, err
@@ -1181,10 +1169,11 @@ func (s *snapshot) AllValidMetadata(ctx context.Context) ([]*source.Metadata, er
11811169
}
11821170

11831171
s.mu.Lock()
1184-
defer s.mu.Unlock()
1172+
g := s.meta
1173+
s.mu.Unlock()
11851174

1186-
var meta []*source.Metadata
1187-
for _, m := range s.meta.metadata {
1175+
meta := make([]*source.Metadata, 0, len(g.metadata))
1176+
for _, m := range g.metadata {
11881177
meta = append(meta, m)
11891178
}
11901179
return meta, nil
@@ -1281,11 +1270,7 @@ func (s *snapshot) clearShouldLoad(scopes ...loadScope) {
12811270
// noValidMetadataForURILocked reports whether there is any valid metadata for
12821271
// the given URI.
12831272
func (s *snapshot) noValidMetadataForURILocked(uri span.URI) bool {
1284-
ids, ok := s.meta.ids[uri]
1285-
if !ok {
1286-
return true
1287-
}
1288-
for _, id := range ids {
1273+
for _, id := range s.meta.ids[uri] {
12891274
if _, ok := s.meta.metadata[id]; ok {
12901275
return false
12911276
}
@@ -1483,12 +1468,8 @@ func (s *snapshot) awaitLoadedAllErrors(ctx context.Context) *source.CriticalErr
14831468
// TODO(rfindley): Should we be more careful about returning the
14841469
// initialization error? Is it possible for the initialization error to be
14851470
// corrected without a successful reinitialization?
1486-
s.mu.Lock()
1487-
initializedErr := s.initializedErr
1488-
s.mu.Unlock()
1489-
1490-
if initializedErr != nil {
1491-
return initializedErr
1471+
if err := s.getInitializationError(); err != nil {
1472+
return err
14921473
}
14931474

14941475
// TODO(rfindley): revisit this handling. Calling reloadWorkspace with a
@@ -1928,7 +1909,25 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
19281909
addRevDeps(id, invalidateMetadata)
19291910
}
19301911

1931-
result.invalidatePackagesLocked(idsToInvalidate)
1912+
// Delete invalidated package type information.
1913+
for id := range idsToInvalidate {
1914+
for _, mode := range source.AllParseModes {
1915+
key := packageKey{mode, id}
1916+
result.packages.Delete(key)
1917+
}
1918+
}
1919+
1920+
// Delete invalidated analysis actions.
1921+
var actionsToDelete []actionKey
1922+
result.actions.Range(func(k, _ interface{}) {
1923+
key := k.(actionKey)
1924+
if _, ok := idsToInvalidate[key.pkgid]; ok {
1925+
actionsToDelete = append(actionsToDelete, key)
1926+
}
1927+
})
1928+
for _, key := range actionsToDelete {
1929+
result.actions.Delete(key)
1930+
}
19321931

19331932
// If a file has been deleted, we must delete metadata for all packages
19341933
// containing that file.
@@ -2080,35 +2079,6 @@ func invalidatedPackageIDs(uri span.URI, known map[span.URI][]PackageID, package
20802079
return invalidated
20812080
}
20822081

2083-
// invalidatePackagesLocked deletes data associated with the given package IDs.
2084-
//
2085-
// Note: all keys in the ids map are invalidated, regardless of the
2086-
// corresponding value.
2087-
//
2088-
// s.mu must be held while calling this function.
2089-
func (s *snapshot) invalidatePackagesLocked(ids map[PackageID]bool) {
2090-
// Delete invalidated package type information.
2091-
for id := range ids {
2092-
for _, mode := range source.AllParseModes {
2093-
key := packageKey{mode, id}
2094-
s.packages.Delete(key)
2095-
}
2096-
}
2097-
2098-
// Copy actions.
2099-
// TODO(adonovan): opt: avoid iteration over s.actions.
2100-
var actionsToDelete []actionKey
2101-
s.actions.Range(func(k, _ interface{}) {
2102-
key := k.(actionKey)
2103-
if _, ok := ids[key.pkgid]; ok {
2104-
actionsToDelete = append(actionsToDelete, key)
2105-
}
2106-
})
2107-
for _, key := range actionsToDelete {
2108-
s.actions.Delete(key)
2109-
}
2110-
}
2111-
21122082
// fileWasSaved reports whether the FileHandle passed in has been saved. It
21132083
// accomplishes this by checking to see if the original and current FileHandles
21142084
// are both overlays, and if the current FileHandle is saved while the original

gopls/internal/lsp/code_action.go

+3-2
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ func (s *Server) codeAction(ctx context.Context, params *protocol.CodeActionPara
212212
}
213213

214214
if wanted[protocol.RefactorExtract] {
215-
fixes, err := extractionFixes(ctx, snapshot, pkg, uri, params.Range)
215+
fixes, err := extractionFixes(ctx, snapshot, uri, params.Range)
216216
if err != nil {
217217
return nil, err
218218
}
@@ -305,14 +305,15 @@ func importDiagnostics(fix *imports.ImportFix, diagnostics []protocol.Diagnostic
305305
return results
306306
}
307307

308-
func extractionFixes(ctx context.Context, snapshot source.Snapshot, pkg source.Package, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) {
308+
func extractionFixes(ctx context.Context, snapshot source.Snapshot, uri span.URI, rng protocol.Range) ([]protocol.CodeAction, error) {
309309
if rng.Start == rng.End {
310310
return nil, nil
311311
}
312312
fh, err := snapshot.GetFile(ctx, uri)
313313
if err != nil {
314314
return nil, err
315315
}
316+
// TODO(adonovan): opt: avoid package loading; only parsing is needed.
316317
_, pgf, err := source.GetParsedFile(ctx, snapshot, fh, source.NarrowestPackage)
317318
if err != nil {
318319
return nil, fmt.Errorf("getting file for Identifier: %w", err)

gopls/internal/lsp/command.go

+2
Original file line numberDiff line numberDiff line change
@@ -690,6 +690,7 @@ func (c *commandHandler) ToggleGCDetails(ctx context.Context, args command.URIAr
690690
progress: "Toggling GC Details",
691691
forURI: args.URI,
692692
}, func(ctx context.Context, deps commandDeps) error {
693+
// TODO(adonovan): opt: avoid loading type-checked package; only Metadata is needed.
693694
pkg, err := deps.snapshot.PackageForFile(ctx, deps.fh.URI(), source.TypecheckWorkspace, source.NarrowestPackage)
694695
if err != nil {
695696
return err
@@ -756,6 +757,7 @@ func (c *commandHandler) ListImports(ctx context.Context, args command.URIArg) (
756757
err := c.run(ctx, commandConfig{
757758
forURI: args.URI,
758759
}, func(ctx context.Context, deps commandDeps) error {
760+
// TODO(adonovan): opt: avoid loading type-checked package; only Metadata is needed.
759761
pkg, err := deps.snapshot.PackageForFile(ctx, args.URI.SpanURI(), source.TypecheckWorkspace, source.NarrowestPackage)
760762
if err != nil {
761763
return err

gopls/internal/lsp/link.go

+2
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,7 @@ func modLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandl
103103
func goLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) ([]protocol.DocumentLink, error) {
104104
view := snapshot.View()
105105
// We don't actually need type information, so any typecheck mode is fine.
106+
// TODO(adonovan): opt: avoid loading type-checked package; only Metadata is needed.
106107
pkg, err := snapshot.PackageForFile(ctx, fh.URI(), source.TypecheckWorkspace, source.WidestPackage)
107108
if err != nil {
108109
return nil, err
@@ -174,6 +175,7 @@ func goLinks(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle
174175
}
175176

176177
func moduleAtVersion(targetImportPath source.ImportPath, pkg source.Package) (string, string, bool) {
178+
// TODO(adonovan): opt: avoid need for package; use Metadata.DepsByImportPath only.
177179
impPkg, err := pkg.ResolveImportPath(targetImportPath)
178180
if err != nil {
179181
return "", "", false

0 commit comments

Comments
 (0)