Skip to content

Commit cd0288f

Browse files
committed
internal/lsp/cache: invalidate analysis results on packages.Load
Most invalidation happens in snapshot.clone, but to be safe we were also invalidating package data when new metadata is set via packages.Load. At the time, I wasn't sure why this was necessary. Now I understand: with experimentalUseInvalidMetadata it is possible that we re-compute invalidated data using stale metadata in between the initial clone and the subsequent reload. I noticed that it was also possible to have stale analysis results after the Load results arrive. Fix this by invalidating analysis results on Load, in addition to packages. Factor out invalidation into a new helper method. Since we believe this may fix analyzer panics, re-enable strict handling of analysis panics during tests. For golang/go#56035 For golang/go#54762 For golang/go#42857 Change-Id: I8c28e0700f8c16c58df4ecf60f6127b1c05d6dc5 Reviewed-on: https://go-review.googlesource.com/c/tools/+/420538 Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Alan Donovan <[email protected]>
1 parent 906c733 commit cd0288f

File tree

4 files changed

+48
-37
lines changed

4 files changed

+48
-37
lines changed

gopls/internal/lsp/cache/analysis.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -365,10 +365,10 @@ func actionImpl(ctx context.Context, snapshot *snapshot, deps []*actionHandle, a
365365
if r := recover(); r != nil {
366366
// An Analyzer crashed. This is often merely a symptom
367367
// of a problem in package loading.
368-
// Now that we have a theory of these crashes,
369-
// we disable the check to stop flakes from being a nuisance.
370-
// TODO(adonovan): re-enable it when plausibly fixed.
371-
const strict = false
368+
//
369+
// We believe that CL 420538 may have fixed these crashes, so enable
370+
// strict checks in tests.
371+
const strict = true
372372
if strict && bug.PanicOnBugs && analyzer.Name != "fact_purity" {
373373
// During testing, crash. See issues 54762, 56035.
374374
// But ignore analyzers with known crash bugs:

gopls/internal/lsp/cache/graph.go

+4-4
Original file line numberDiff line numberDiff line change
@@ -134,20 +134,20 @@ func (g *metadataGraph) build() {
134134
//
135135
// If includeInvalid is false, the algorithm ignores packages with invalid
136136
// metadata (including those in the given list of ids).
137-
func (g *metadataGraph) reverseTransitiveClosure(includeInvalid bool, ids ...PackageID) map[PackageID]struct{} {
138-
seen := make(map[PackageID]struct{})
137+
func (g *metadataGraph) reverseTransitiveClosure(includeInvalid bool, ids ...PackageID) map[PackageID]bool {
138+
seen := make(map[PackageID]bool)
139139
var visitAll func([]PackageID)
140140
visitAll = func(ids []PackageID) {
141141
for _, id := range ids {
142-
if _, ok := seen[id]; ok {
142+
if seen[id] {
143143
continue
144144
}
145145
m := g.metadata[id]
146146
// Only use invalid metadata if we support it.
147147
if m == nil || !(m.Valid || includeInvalid) {
148148
continue
149149
}
150-
seen[id] = struct{}{}
150+
seen[id] = true
151151
visitAll(g.importedBy[id])
152152
}
153153
}

gopls/internal/lsp/cache/load.go

+10-9
Original file line numberDiff line numberDiff line change
@@ -228,16 +228,17 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...interf
228228
s.meta = s.meta.Clone(updates)
229229
s.resetIsActivePackageLocked()
230230

231-
// Invalidate any packages we may have associated with this metadata.
231+
// Invalidate any packages and analysis results we may have associated with
232+
// this metadata.
232233
//
233-
// TODO(rfindley): this should not be necessary, as we should have already
234-
// invalidated in snapshot.clone.
235-
for id := range invalidatedPackages {
236-
for _, mode := range source.AllParseModes {
237-
key := packageKey{mode, id}
238-
s.packages.Delete(key)
239-
}
240-
}
234+
// Generally speaking we should have already invalidated these results in
235+
// snapshot.clone, but with experimentalUseInvalidMetadata is may be possible
236+
// that we have re-computed stale results before the reload completes. In
237+
// this case, we must re-invalidate here.
238+
//
239+
// TODO(golang/go#54180): if we decide to make experimentalUseInvalidMetadata
240+
// obsolete, we should avoid this invalidation.
241+
s.invalidatePackagesLocked(invalidatedPackages)
241242

242243
s.workspacePackages = computeWorkspacePackagesLocked(s, s.meta)
243244
s.dumpWorkspace("load")

gopls/internal/lsp/cache/snapshot.go

+30-20
Original file line numberDiff line numberDiff line change
@@ -1856,26 +1856,7 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
18561856
addRevDeps(id, invalidateMetadata)
18571857
}
18581858

1859-
// Delete invalidated package type information.
1860-
for id := range idsToInvalidate {
1861-
for _, mode := range source.AllParseModes {
1862-
key := packageKey{mode, id}
1863-
result.packages.Delete(key)
1864-
}
1865-
}
1866-
1867-
// Copy actions.
1868-
// TODO(adonovan): opt: avoid iteration over s.actions.
1869-
var actionsToDelete []actionKey
1870-
s.actions.Range(func(k, _ interface{}) {
1871-
key := k.(actionKey)
1872-
if _, ok := idsToInvalidate[key.pkgid]; ok {
1873-
actionsToDelete = append(actionsToDelete, key)
1874-
}
1875-
})
1876-
for _, key := range actionsToDelete {
1877-
result.actions.Delete(key)
1878-
}
1859+
result.invalidatePackagesLocked(idsToInvalidate)
18791860

18801861
// If a file has been deleted, we must delete metadata for all packages
18811862
// containing that file.
@@ -2050,6 +2031,35 @@ func invalidatedPackageIDs(uri span.URI, known map[span.URI][]PackageID, package
20502031
return invalidated
20512032
}
20522033

2034+
// invalidatePackagesLocked deletes data associated with the given package IDs.
2035+
//
2036+
// Note: all keys in the ids map are invalidated, regardless of the
2037+
// corresponding value.
2038+
//
2039+
// s.mu must be held while calling this function.
2040+
func (s *snapshot) invalidatePackagesLocked(ids map[PackageID]bool) {
2041+
// Delete invalidated package type information.
2042+
for id := range ids {
2043+
for _, mode := range source.AllParseModes {
2044+
key := packageKey{mode, id}
2045+
s.packages.Delete(key)
2046+
}
2047+
}
2048+
2049+
// Copy actions.
2050+
// TODO(adonovan): opt: avoid iteration over s.actions.
2051+
var actionsToDelete []actionKey
2052+
s.actions.Range(func(k, _ interface{}) {
2053+
key := k.(actionKey)
2054+
if _, ok := ids[key.pkgid]; ok {
2055+
actionsToDelete = append(actionsToDelete, key)
2056+
}
2057+
})
2058+
for _, key := range actionsToDelete {
2059+
s.actions.Delete(key)
2060+
}
2061+
}
2062+
20532063
// fileWasSaved reports whether the FileHandle passed in has been saved. It
20542064
// accomplishes this by checking to see if the original and current FileHandles
20552065
// are both overlays, and if the current FileHandle is saved while the original

0 commit comments

Comments
 (0)