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 <adonovan@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
diff --git a/gopls/internal/lsp/cache/snapshot.go b/gopls/internal/lsp/cache/snapshot.go
index 369235f..1f738c6 100644
--- a/gopls/internal/lsp/cache/snapshot.go
+++ b/gopls/internal/lsp/cache/snapshot.go
@@ -100,10 +100,10 @@
// It may be invalidated when a file's content changes.
//
// Invariants to preserve:
- // - packages.Get(id).m.Metadata == meta.metadata[id].Metadata for all ids
+ // - packages.Get(id).meta == meta.metadata[id] for all ids
// - if a package is in packages, then all of its dependencies should also
// be in packages, unless there is a missing import
- packages *persistent.Map // from packageKey to *memoize.Promise[*packageHandle]
+ packages *persistent.Map // from packageKey to *packageHandle
// isActivePackageCache maps package ID to the cached value if it is active or not.
// It may be invalidated when metadata changes or a new file is opened or closed.
@@ -655,6 +655,8 @@
func (s *snapshot) PackageForFile(ctx context.Context, uri span.URI, mode source.TypecheckMode, pkgPolicy source.PackageFilter) (source.Package, error) {
ctx = event.Label(ctx, tag.URI.Of(uri))
+ // TODO(adonovan): opt: apply pkgPolicy to the list of
+ // Metadatas before initiating loading of any package.
phs, err := s.packageHandlesForFile(ctx, uri, mode, false)
if err != nil {
return nil, err
@@ -699,24 +701,24 @@
if kind := s.view.FileKind(fh); kind != source.Go {
return nil, fmt.Errorf("no packages for non-Go file %s (%v)", uri, kind)
}
- knownIDs, err := s.getOrLoadIDsForURI(ctx, uri)
+ metas, err := s.MetadataForFile(ctx, uri)
if err != nil {
return nil, err
}
var phs []*packageHandle
- for _, id := range knownIDs {
+ for _, m := range metas {
// Filter out any intermediate test variants. We typically aren't
// interested in these packages for file= style queries.
- if m := s.getMetadata(id); m != nil && m.IsIntermediateTestVariant() && !withIntermediateTestVariants {
+ if m.IsIntermediateTestVariant() && !withIntermediateTestVariants {
continue
}
parseMode := source.ParseFull
if mode == source.TypecheckWorkspace {
- parseMode = s.workspaceParseMode(id)
+ parseMode = s.workspaceParseMode(m.ID)
}
- ph, err := s.buildPackageHandle(ctx, id, parseMode)
+ ph, err := s.buildPackageHandle(ctx, m.ID, parseMode)
if err != nil {
return nil, err
}
@@ -725,12 +727,10 @@
return phs, nil
}
-// getOrLoadIDsForURI returns package IDs associated with the file uri. If no
-// such packages exist or if they are known to be stale, it reloads the file.
-//
-// If experimentalUseInvalidMetadata is set, this function may return package
-// IDs with invalid metadata.
-func (s *snapshot) getOrLoadIDsForURI(ctx context.Context, uri span.URI) ([]PackageID, error) {
+// MetadataForFile returns the Metadata for each package associated
+// with the file uri. If no such package exists or if they are known
+// to be stale, it reloads metadata for the file.
+func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]*source.Metadata, error) {
s.mu.Lock()
// Start with the set of package associations derived from the last load.
@@ -772,23 +772,31 @@
s.clearShouldLoad(scope)
// Don't return an error here, as we may still return stale IDs.
- // Furthermore, the result of getOrLoadIDsForURI should be consistent upon
+ // Furthermore, the result of MetadataForFile should be consistent upon
// subsequent calls, even if the file is marked as unloadable.
if err != nil && !errors.Is(err, errNoPackages) {
- event.Error(ctx, "getOrLoadIDsForURI", err)
+ event.Error(ctx, "MetadataForFile", err)
}
}
+ // Retrieve the metadata.
s.mu.Lock()
+ defer s.mu.Unlock()
ids = s.meta.ids[uri]
- // metadata is only ever added by loading, so if we get here and still have
- // no ids, uri is unloadable.
+ metas := make([]*source.Metadata, len(ids))
+ for i, id := range ids {
+ metas[i] = s.meta.metadata[id]
+ if metas[i] == nil {
+ panic("nil metadata")
+ }
+ }
+ // Metadata is only ever added by loading,
+ // so if we get here and still have
+ // no IDs, uri is unloadable.
if !unloadable && len(ids) == 0 {
s.unloadableFiles[uri] = struct{}{}
}
- s.mu.Unlock()
-
- return ids, nil
+ return metas, nil
}
func (s *snapshot) GetReverseDependencies(ctx context.Context, id PackageID) ([]source.Package, error) {
@@ -1135,37 +1143,17 @@
return result
}
-func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]*source.Metadata, error) {
- knownIDs, err := s.getOrLoadIDsForURI(ctx, uri)
- if err != nil {
- return nil, err
- }
- var mds []*source.Metadata
- for _, id := range knownIDs {
- md := s.getMetadata(id)
- // TODO(rfindley): knownIDs and metadata should be in sync, but existing
- // code is defensive of nil metadata.
- if md != nil {
- mds = append(mds, md)
- }
- }
- return mds, nil
-}
-
func (s *snapshot) KnownPackages(ctx context.Context) ([]source.Package, error) {
if err := s.awaitLoaded(ctx); err != nil {
return nil, err
}
- ids := make([]source.PackageID, 0, len(s.meta.metadata))
s.mu.Lock()
- for id := range s.meta.metadata {
- ids = append(ids, id)
- }
+ g := s.meta
s.mu.Unlock()
- pkgs := make([]source.Package, 0, len(ids))
- for _, id := range ids {
+ pkgs := make([]source.Package, 0, len(g.metadata))
+ for id := range g.metadata {
pkg, err := s.checkedPackage(ctx, id, s.workspaceParseMode(id))
if err != nil {
return nil, err
@@ -1181,10 +1169,11 @@
}
s.mu.Lock()
- defer s.mu.Unlock()
+ g := s.meta
+ s.mu.Unlock()
- var meta []*source.Metadata
- for _, m := range s.meta.metadata {
+ meta := make([]*source.Metadata, 0, len(g.metadata))
+ for _, m := range g.metadata {
meta = append(meta, m)
}
return meta, nil
@@ -1281,11 +1270,7 @@
// noValidMetadataForURILocked reports whether there is any valid metadata for
// the given URI.
func (s *snapshot) noValidMetadataForURILocked(uri span.URI) bool {
- ids, ok := s.meta.ids[uri]
- if !ok {
- return true
- }
- for _, id := range ids {
+ for _, id := range s.meta.ids[uri] {
if _, ok := s.meta.metadata[id]; ok {
return false
}
@@ -1483,12 +1468,8 @@
// TODO(rfindley): Should we be more careful about returning the
// initialization error? Is it possible for the initialization error to be
// corrected without a successful reinitialization?
- s.mu.Lock()
- initializedErr := s.initializedErr
- s.mu.Unlock()
-
- if initializedErr != nil {
- return initializedErr
+ if err := s.getInitializationError(); err != nil {
+ return err
}
// TODO(rfindley): revisit this handling. Calling reloadWorkspace with a
@@ -1928,7 +1909,25 @@
addRevDeps(id, invalidateMetadata)
}
- result.invalidatePackagesLocked(idsToInvalidate)
+ // Delete invalidated package type information.
+ for id := range idsToInvalidate {
+ for _, mode := range source.AllParseModes {
+ key := packageKey{mode, id}
+ result.packages.Delete(key)
+ }
+ }
+
+ // Delete invalidated analysis actions.
+ var actionsToDelete []actionKey
+ result.actions.Range(func(k, _ interface{}) {
+ key := k.(actionKey)
+ if _, ok := idsToInvalidate[key.pkgid]; ok {
+ actionsToDelete = append(actionsToDelete, key)
+ }
+ })
+ for _, key := range actionsToDelete {
+ result.actions.Delete(key)
+ }
// If a file has been deleted, we must delete metadata for all packages
// containing that file.
@@ -2080,35 +2079,6 @@
return invalidated
}
-// invalidatePackagesLocked deletes data associated with the given package IDs.
-//
-// Note: all keys in the ids map are invalidated, regardless of the
-// corresponding value.
-//
-// s.mu must be held while calling this function.
-func (s *snapshot) invalidatePackagesLocked(ids map[PackageID]bool) {
- // Delete invalidated package type information.
- for id := range ids {
- for _, mode := range source.AllParseModes {
- key := packageKey{mode, id}
- s.packages.Delete(key)
- }
- }
-
- // Copy actions.
- // TODO(adonovan): opt: avoid iteration over s.actions.
- var actionsToDelete []actionKey
- s.actions.Range(func(k, _ interface{}) {
- key := k.(actionKey)
- if _, ok := ids[key.pkgid]; ok {
- actionsToDelete = append(actionsToDelete, key)
- }
- })
- for _, key := range actionsToDelete {
- s.actions.Delete(key)
- }
-}
-
// fileWasSaved reports whether the FileHandle passed in has been saved. It
// accomplishes this by checking to see if the original and current FileHandles
// are both overlays, and if the current FileHandle is saved while the original