gopls/internal/lsp/cache: stream package results
As we move toward incremental evaluation of the package key and precise
pruning, rewrite the type checking pass to formalize the concept of a
pre- and post- package visitor. Whereas previously we would do an
initial pass outside of type-checking to load data (xrefs, diagnostics,
etc), we now integrate loads checks with the batch operation, calling
a pre- func immediately before type-checking, and a post- func
immediately afterward.
This change was originally intended only to move us toward precise
pruning, but inadvertently simplified and clarified many aspects of
type-checking. Namely, it:
- resulted in eliminating all state from the type check batch;
typeCheckBatch.mu is deleted
- allows avoiding double-encoding of diagnostics and method sets, to fit
the previous APIs. This results in a significant improvement in the
methodsets benchmarks
- significantly simplifies the logic of the type-checking batch
- significantly reduces the high water mark during the IWL, as the GC
can clean up the packages that are no longer pinned to the batch
For golang/go#57987
Change-Id: Iad1073f32cea0a7e26567d94cdd3ed01ff60ad04
Reviewed-on: https://go-review.googlesource.com/c/tools/+/476956
gopls-CI: kokoro <noreply+kokoro@google.com>
Run-TryBot: Robert Findley <rfindley@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Reviewed-by: Alan Donovan <adonovan@google.com>
diff --git a/gopls/internal/lsp/cache/snapshot.go b/gopls/internal/lsp/cache/snapshot.go
index 406a428..bf026b6 100644
--- a/gopls/internal/lsp/cache/snapshot.go
+++ b/gopls/internal/lsp/cache/snapshot.go
@@ -29,6 +29,7 @@
"golang.org/x/sync/errgroup"
"golang.org/x/tools/go/packages"
"golang.org/x/tools/go/types/objectpath"
+ "golang.org/x/tools/gopls/internal/lsp/filecache"
"golang.org/x/tools/gopls/internal/lsp/protocol"
"golang.org/x/tools/gopls/internal/lsp/source"
"golang.org/x/tools/gopls/internal/lsp/source/methodsets"
@@ -633,34 +634,49 @@
)
func (s *snapshot) PackageDiagnostics(ctx context.Context, ids ...PackageID) (map[span.URI][]*source.Diagnostic, error) {
- // TODO(rfindley): opt: avoid unnecessary encode->decode after type-checking.
- data, err := s.getPackageData(ctx, diagnosticsKind, ids, func(p *syntaxPackage) []byte {
- return encodeDiagnostics(p.diagnostics)
- })
+ var mu sync.Mutex
perFile := make(map[span.URI][]*source.Diagnostic)
- for _, data := range data {
- if data != nil {
- for _, diag := range data.m.Diagnostics {
- perFile[diag.URI] = append(perFile[diag.URI], diag)
- }
- diags := decodeDiagnostics(data.data)
- for _, diag := range diags {
- perFile[diag.URI] = append(perFile[diag.URI], diag)
- }
+ collect := func(diags []*source.Diagnostic) {
+ mu.Lock()
+ defer mu.Unlock()
+ for _, diag := range diags {
+ perFile[diag.URI] = append(perFile[diag.URI], diag)
}
}
- return perFile, err
+ pre := func(i int, ph *packageHandle) bool {
+ data, err := filecache.Get(diagnosticsKind, ph.key)
+ if err == nil { // hit
+ collect(ph.m.Diagnostics)
+ collect(decodeDiagnostics(data))
+ return false
+ } else if err != filecache.ErrNotFound {
+ event.Error(ctx, "reading diagnostics from filecache", err)
+ }
+ return true
+ }
+ post := func(_ int, pkg *Package) {
+ collect(pkg.m.Diagnostics)
+ collect(pkg.pkg.diagnostics)
+ }
+ return perFile, s.forEachPackage(ctx, ids, pre, post)
}
func (s *snapshot) References(ctx context.Context, ids ...PackageID) ([]source.XrefIndex, error) {
- data, err := s.getPackageData(ctx, xrefsKind, ids, func(p *syntaxPackage) []byte { return p.xrefs })
indexes := make([]source.XrefIndex, len(ids))
- for i, data := range data {
- if data != nil {
- indexes[i] = XrefIndex{m: data.m, data: data.data}
+ pre := func(i int, ph *packageHandle) bool {
+ data, err := filecache.Get(xrefsKind, ph.key)
+ if err == nil { // hit
+ indexes[i] = XrefIndex{m: ph.m, data: data}
+ return false
+ } else if err != filecache.ErrNotFound {
+ event.Error(ctx, "reading xrefs from filecache", err)
}
+ return true
}
- return indexes, err
+ post := func(i int, pkg *Package) {
+ indexes[i] = XrefIndex{m: pkg.m, data: pkg.pkg.xrefs}
+ }
+ return indexes, s.forEachPackage(ctx, ids, pre, post)
}
// An XrefIndex is a helper for looking up a package in a given package.
@@ -674,21 +690,21 @@
}
func (s *snapshot) MethodSets(ctx context.Context, ids ...PackageID) ([]*methodsets.Index, error) {
- // TODO(rfindley): opt: avoid unnecessary encode->decode after type-checking.
- data, err := s.getPackageData(ctx, methodSetsKind, ids, func(p *syntaxPackage) []byte {
- return p.methodsets.Encode()
- })
indexes := make([]*methodsets.Index, len(ids))
- for i, data := range data {
- if data != nil {
- indexes[i] = methodsets.Decode(data.data)
- } else if ids[i] == "unsafe" {
- indexes[i] = &methodsets.Index{}
- } else {
- panic(fmt.Sprintf("nil data for %s", ids[i]))
+ pre := func(i int, ph *packageHandle) bool {
+ data, err := filecache.Get(methodSetsKind, ph.key)
+ if err == nil { // hit
+ indexes[i] = methodsets.Decode(data)
+ return false
+ } else if err != filecache.ErrNotFound {
+ event.Error(ctx, "reading methodsets from filecache", err)
}
+ return true
}
- return indexes, err
+ post := func(i int, pkg *Package) {
+ indexes[i] = pkg.pkg.methodsets
+ }
+ return indexes, s.forEachPackage(ctx, ids, pre, post)
}
func (s *snapshot) MetadataForFile(ctx context.Context, uri span.URI) ([]*source.Metadata, error) {