Skip to content

Commit

Permalink
gopls/internal/lsp/cache: compute xrefs and methodsets asynchronously
Browse files Browse the repository at this point in the history
No need to wait on xrefs or methodsets while performing
latency-sensitive operations on packages.

For golang/go#53992

Change-Id: I9ea65269a8c1e604fb99ed8b25e14db79f179576
Reviewed-on: https://go-review.googlesource.com/c/tools/+/503015
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Gopher Robot <gobot@golang.org>
Run-TryBot: Robert Findley <rfindley@google.com>
Reviewed-by: Alan Donovan <adonovan@google.com>
  • Loading branch information
findleyr committed Jun 14, 2023
1 parent 27dbf85 commit f394d45
Show file tree
Hide file tree
Showing 3 changed files with 37 additions and 23 deletions.
30 changes: 14 additions & 16 deletions gopls/internal/lsp/cache/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,7 @@ import (
"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"
"golang.org/x/tools/gopls/internal/lsp/source/typerefs"
"golang.org/x/tools/gopls/internal/lsp/source/xrefs"
"golang.org/x/tools/gopls/internal/span"
"golang.org/x/tools/internal/event"
"golang.org/x/tools/internal/event/tag"
Expand All @@ -44,6 +42,8 @@ const (
preserveImportGraph = true // hold on to the import graph for open packages
)

type unit = struct{}

// A typeCheckBatch holds data for a logical type-checking operation, which may
// type-check many unrelated packages.
//
Expand All @@ -56,7 +56,7 @@ type typeCheckBatch struct {
handles map[PackageID]*packageHandle
parseCache *parseCache
fset *token.FileSet // describes all parsed or imported files
cpulimit chan struct{} // concurrency limiter for CPU-bound operations
cpulimit chan unit // concurrency limiter for CPU-bound operations

mu sync.Mutex
syntaxPackages map[PackageID]*futurePackage // results of processing a requested package; may hold (nil, nil)
Expand All @@ -69,7 +69,7 @@ type typeCheckBatch struct {
// The goroutine that creates the futurePackage is responsible for evaluating
// its value, and closing the done channel.
type futurePackage struct {
done chan struct{}
done chan unit
v pkgOrErr
}

Expand Down Expand Up @@ -154,7 +154,7 @@ func (s *snapshot) getImportGraph(ctx context.Context) *importGraph {
// for the work to be done.
done := s.importGraphDone
if done == nil {
done = make(chan struct{})
done = make(chan unit)
s.importGraphDone = done
release := s.Acquire() // must acquire to use the snapshot asynchronously
go func() {
Expand Down Expand Up @@ -360,7 +360,7 @@ func (s *snapshot) forEachPackageInternal(ctx context.Context, importGraph *impo
handles: handles,
fset: fileSetWithBase(reservedForParsing),
syntaxIndex: make(map[PackageID]int),
cpulimit: make(chan struct{}, runtime.GOMAXPROCS(0)),
cpulimit: make(chan unit, runtime.GOMAXPROCS(0)),
syntaxPackages: make(map[PackageID]*futurePackage),
importPackages: make(map[PackageID]*futurePackage),
}
Expand All @@ -369,7 +369,7 @@ func (s *snapshot) forEachPackageInternal(ctx context.Context, importGraph *impo
// Clone the file set every time, to ensure we do not leak files.
b.fset = tokeninternal.CloneFileSet(importGraph.fset)
// Pre-populate future cache with 'done' futures.
done := make(chan struct{})
done := make(chan unit)
close(done)
for id, res := range importGraph.imports {
b.importPackages[id] = &futurePackage{done, res}
Expand Down Expand Up @@ -427,7 +427,7 @@ func (b *typeCheckBatch) getImportPackage(ctx context.Context, id PackageID) (pk
}
}

f = &futurePackage{done: make(chan struct{})}
f = &futurePackage{done: make(chan unit)}
b.importPackages[id] = f
b.mu.Unlock()

Expand Down Expand Up @@ -479,7 +479,7 @@ func (b *typeCheckBatch) handleSyntaxPackage(ctx context.Context, i int, id Pack
return f.v.pkg, f.v.err
}

f = &futurePackage{done: make(chan struct{})}
f = &futurePackage{done: make(chan unit)}
b.syntaxPackages[id] = f
b.mu.Unlock()
defer func() {
Expand Down Expand Up @@ -508,7 +508,7 @@ func (b *typeCheckBatch) handleSyntaxPackage(ctx context.Context, i int, id Pack
select {
case <-ctx.Done():
return nil, ctx.Err()
case b.cpulimit <- struct{}{}:
case b.cpulimit <- unit{}:
defer func() {
<-b.cpulimit // release CPU token
}()
Expand Down Expand Up @@ -637,8 +637,8 @@ func (b *typeCheckBatch) checkPackage(ctx context.Context, ph *packageHandle) (*
// Write package data to disk asynchronously.
go func() {
toCache := map[string][]byte{
xrefsKind: pkg.xrefs,
methodSetsKind: pkg.methodsets.Encode(),
xrefsKind: pkg.xrefs(),
methodSetsKind: pkg.methodsets().Encode(),
diagnosticsKind: encodeDiagnostics(pkg.diagnostics),
}

Expand Down Expand Up @@ -763,13 +763,13 @@ func (s *snapshot) getPackageHandles(ctx context.Context, ids []PackageID) (map[
// Collect all reachable IDs, and create done channels.
// TODO: opt: modify SortPostOrder to make this pre-traversal unnecessary.
var allIDs []PackageID
dones := make(map[PackageID]chan struct{})
dones := make(map[PackageID]chan unit)
var walk func(PackageID)
walk = func(id PackageID) {
if _, ok := dones[id]; ok {
return
}
dones[id] = make(chan struct{})
dones[id] = make(chan unit)
allIDs = append(allIDs, id)
m := meta.metadata[id]
for _, depID := range m.DepsByPkgPath {
Expand Down Expand Up @@ -1262,8 +1262,6 @@ func typeCheckImpl(ctx context.Context, b *typeCheckBatch, inputs typeCheckInput
if err != nil {
return nil, err
}
pkg.methodsets = methodsets.NewIndex(pkg.fset, pkg.types)
pkg.xrefs = xrefs.Index(pkg.compiledGoFiles, pkg.types, pkg.typesInfo)

// Our heuristic for whether to show type checking errors is:
// + If any file was 'fixed', don't show type checking errors as we
Expand Down
26 changes: 21 additions & 5 deletions gopls/internal/lsp/cache/pkg.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@ import (
"go/scanner"
"go/token"
"go/types"
"sync"

"golang.org/x/tools/gopls/internal/lsp/source"
"golang.org/x/tools/gopls/internal/lsp/source/methodsets"
"golang.org/x/tools/gopls/internal/lsp/source/xrefs"
"golang.org/x/tools/gopls/internal/span"
)

Expand Down Expand Up @@ -53,11 +55,25 @@ type syntaxPackage struct {
importMap map[PackagePath]*types.Package
hasFixedFiles bool // if true, AST was sufficiently mangled that we should hide type errors

// TODO(rfindley): opt: xrefs and methodsets do not need to be pinned to the
// package, and perhaps should be computed asynchronously to package
// diagnostics.
xrefs []byte
methodsets *methodsets.Index
xrefsOnce sync.Once
_xrefs []byte // only used by the xrefs method

methodsetsOnce sync.Once
_methodsets *methodsets.Index // only used by the methodsets method
}

func (p *syntaxPackage) xrefs() []byte {
p.xrefsOnce.Do(func() {
p._xrefs = xrefs.Index(p.compiledGoFiles, p.types, p.typesInfo)
})
return p._xrefs
}

func (p *syntaxPackage) methodsets() *methodsets.Index {
p.methodsetsOnce.Do(func() {
p._methodsets = methodsets.NewIndex(p.fset, p.types)
})
return p._methodsets
}

func (p *Package) String() string { return string(p.ph.m.ID) }
Expand Down
4 changes: 2 additions & 2 deletions gopls/internal/lsp/cache/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -688,7 +688,7 @@ func (s *snapshot) References(ctx context.Context, ids ...PackageID) ([]source.X
return true
}
post := func(i int, pkg *Package) {
indexes[i] = XrefIndex{m: pkg.ph.m, data: pkg.pkg.xrefs}
indexes[i] = XrefIndex{m: pkg.ph.m, data: pkg.pkg.xrefs()}
}
return indexes, s.forEachPackage(ctx, ids, pre, post)
}
Expand Down Expand Up @@ -719,7 +719,7 @@ func (s *snapshot) MethodSets(ctx context.Context, ids ...PackageID) ([]*methods
return true
}
post := func(i int, pkg *Package) {
indexes[i] = pkg.pkg.methodsets
indexes[i] = pkg.pkg.methodsets()
}
return indexes, s.forEachPackage(ctx, ids, pre, post)
}
Expand Down

0 comments on commit f394d45

Please sign in to comment.