aboutsummaryrefslogtreecommitdiff
path: root/internal/lsp/cache
diff options
context:
space:
mode:
authorAlan Donovan <adonovan@google.com>2022-07-01 17:28:48 -0400
committerGopher Robot <gobot@golang.org>2022-07-06 20:55:00 +0000
commit36430f4b355177a2580d8df0ec38bbf98556a14b (patch)
treec0f0d0313b0ce317fe8f15e31ce9ec59a2ea47ac /internal/lsp/cache
parentb929f3bf4d57d9023b392ff552aa21a4845e1a07 (diff)
downloadgolang-x-tools-36430f4b355177a2580d8df0ec38bbf98556a14b.tar.gz
internal/lsp/cache: use GetHandle not Bind for actions
This change uses a persistent.Map for actions, just like packages. Actions are now reference counted rather than generational. Also: - note optimization opportunities. - minor cleanups. Change-Id: Ibbac8848a3beb3fe19056a7b160d2185155e7021 Reviewed-on: https://go-review.googlesource.com/c/tools/+/415504 gopls-CI: kokoro <noreply+kokoro@google.com> Auto-Submit: Alan Donovan <adonovan@google.com> Run-TryBot: Alan Donovan <adonovan@google.com> Reviewed-by: Robert Findley <rfindley@google.com> TryBot-Result: Gopher Robot <gobot@golang.org>
Diffstat (limited to 'internal/lsp/cache')
-rw-r--r--internal/lsp/cache/analysis.go30
-rw-r--r--internal/lsp/cache/maps.go24
-rw-r--r--internal/lsp/cache/session.go3
-rw-r--r--internal/lsp/cache/snapshot.go54
4 files changed, 75 insertions, 36 deletions
diff --git a/internal/lsp/cache/analysis.go b/internal/lsp/cache/analysis.go
index db2ca2a8b..4b437858e 100644
--- a/internal/lsp/cache/analysis.go
+++ b/internal/lsp/cache/analysis.go
@@ -85,12 +85,21 @@ type packageFactKey struct {
}
func (s *snapshot) actionHandle(ctx context.Context, id PackageID, a *analysis.Analyzer) (*actionHandle, error) {
+ // TODO(adonovan): opt: this block of code sequentially loads a package
+ // (and all its dependencies), then sequentially creates action handles
+ // for the direct dependencies (whose packages have by then been loaded
+ // as a consequence of ph.check) which does a sequential recursion
+ // down the action graph. Only once all that work is complete do we
+ // put a handle in the cache. As with buildPackageHandle, this does
+ // not exploit the natural parallelism in the problem, and the naive
+ // use of concurrency would lead to an exponential amount of duplicated
+ // work. We should instead use an atomically updated future cache
+ // and a parallel graph traversal.
ph, err := s.buildPackageHandle(ctx, id, source.ParseFull)
if err != nil {
return nil, err
}
- act := s.getActionHandle(id, ph.mode, a)
- if act != nil {
+ if act := s.getActionHandle(id, ph.mode, a); act != nil {
return act, nil
}
if len(ph.key) == 0 {
@@ -100,12 +109,9 @@ func (s *snapshot) actionHandle(ctx context.Context, id PackageID, a *analysis.A
if err != nil {
return nil, err
}
- act = &actionHandle{
- analyzer: a,
- pkg: pkg,
- }
+
+ // Add a dependency on each required analyzer.
var deps []*actionHandle
- // Add a dependency on each required analyzers.
for _, req := range a.Requires {
reqActionHandle, err := s.actionHandle(ctx, id, req)
if err != nil {
@@ -131,7 +137,7 @@ func (s *snapshot) actionHandle(ctx context.Context, id PackageID, a *analysis.A
}
}
- h := s.generation.Bind(buildActionKey(a, ph), func(ctx context.Context, arg memoize.Arg) interface{} {
+ handle, release := s.generation.GetHandle(buildActionKey(a, ph), func(ctx context.Context, arg memoize.Arg) interface{} {
snapshot := arg.(*snapshot)
// Analyze dependencies first.
results, err := execAll(ctx, snapshot, deps)
@@ -142,9 +148,13 @@ func (s *snapshot) actionHandle(ctx context.Context, id PackageID, a *analysis.A
}
return runAnalysis(ctx, snapshot, a, pkg, results)
})
- act.handle = h
- act = s.addActionHandle(act)
+ act := &actionHandle{
+ analyzer: a,
+ pkg: pkg,
+ handle: handle,
+ }
+ act = s.addActionHandle(act, release)
return act, nil
}
diff --git a/internal/lsp/cache/maps.go b/internal/lsp/cache/maps.go
index af0417771..f8e03057c 100644
--- a/internal/lsp/cache/maps.go
+++ b/internal/lsp/cache/maps.go
@@ -197,16 +197,18 @@ type packagesMap struct {
func newPackagesMap() packagesMap {
return packagesMap{
impl: persistent.NewMap(func(a, b interface{}) bool {
- left := a.(packageKey)
- right := b.(packageKey)
- if left.mode != right.mode {
- return left.mode < right.mode
- }
- return left.id < right.id
+ return packageKeyLess(a.(packageKey), b.(packageKey))
}),
}
}
+func packageKeyLess(x, y packageKey) bool {
+ if x.mode != y.mode {
+ return x.mode < y.mode
+ }
+ return x.id < y.id
+}
+
func (m packagesMap) Clone() packagesMap {
return packagesMap{
impl: m.impl.Clone(),
@@ -285,3 +287,13 @@ func (s knownDirsSet) Insert(key span.URI) {
func (s knownDirsSet) Remove(key span.URI) {
s.impl.Delete(key)
}
+
+// actionKeyLessInterface is the less-than relation for actionKey
+// values wrapped in an interface.
+func actionKeyLessInterface(a, b interface{}) bool {
+ x, y := a.(actionKey), b.(actionKey)
+ if x.analyzer.Name != y.analyzer.Name {
+ return x.analyzer.Name < y.analyzer.Name
+ }
+ return packageKeyLess(x.pkg, y.pkg)
+}
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index aca46dd2a..98d3c2504 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -16,6 +16,7 @@ import (
"golang.org/x/tools/internal/imports"
"golang.org/x/tools/internal/lsp/progress"
"golang.org/x/tools/internal/lsp/source"
+ "golang.org/x/tools/internal/persistent"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/xcontext"
)
@@ -238,7 +239,7 @@ func (s *Session) createView(ctx context.Context, name string, folder span.URI,
goFiles: newGoFilesMap(),
parseKeysByURI: newParseKeysByURIMap(),
symbols: make(map[span.URI]*symbolHandle),
- actions: make(map[actionKey]*actionHandle),
+ actions: persistent.NewMap(actionKeyLessInterface),
workspacePackages: make(map[PackageID]PackagePath),
unloadableFiles: make(map[span.URI]struct{}),
parseModHandles: make(map[span.URI]*parseModHandle),
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index 36dcafeac..93316653a 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -36,6 +36,7 @@ import (
"golang.org/x/tools/internal/lsp/source"
"golang.org/x/tools/internal/memoize"
"golang.org/x/tools/internal/packagesinternal"
+ "golang.org/x/tools/internal/persistent"
"golang.org/x/tools/internal/span"
"golang.org/x/tools/internal/typesinternal"
)
@@ -87,7 +88,7 @@ type snapshot struct {
// TODO(rfindley): consider merging this with files to reduce burden on clone.
symbols map[span.URI]*symbolHandle
- // packages maps a packageKey to a set of packageHandles to which that file belongs.
+ // packages maps a packageKey to a *packageHandle.
// It may be invalidated when a file's content changes.
packages packagesMap
@@ -95,8 +96,9 @@ type snapshot struct {
// It may be invalidated when metadata changes or a new file is opened or closed.
isActivePackageCache isActivePackageCacheMap
- // actions maps an actionkey to its actionHandle.
- actions map[actionKey]*actionHandle
+ // actions maps an actionKey to the handle for the future
+ // result of execution an analysis pass on a package.
+ actions *persistent.Map // from actionKey to *actionHandle
// workspacePackages contains the workspace's packages, which are loaded
// when the view is created.
@@ -149,6 +151,7 @@ func (s *snapshot) Destroy(destroyedBy string) {
s.generation.Destroy(destroyedBy)
s.packages.Destroy()
s.isActivePackageCache.Destroy()
+ s.actions.Destroy()
s.files.Destroy()
s.goFiles.Destroy()
s.parseKeysByURI.Destroy()
@@ -1177,9 +1180,6 @@ func (s *snapshot) addSymbolHandle(uri span.URI, sh *symbolHandle) *symbolHandle
}
func (s *snapshot) getActionHandle(id PackageID, m source.ParseMode, a *analysis.Analyzer) *actionHandle {
- s.mu.Lock()
- defer s.mu.Unlock()
-
key := actionKey{
pkg: packageKey{
id: id,
@@ -1187,13 +1187,18 @@ func (s *snapshot) getActionHandle(id PackageID, m source.ParseMode, a *analysis
},
analyzer: a,
}
- return s.actions[key]
-}
-func (s *snapshot) addActionHandle(ah *actionHandle) *actionHandle {
s.mu.Lock()
defer s.mu.Unlock()
+ ah, ok := s.actions.Get(key)
+ if !ok {
+ return nil
+ }
+ return ah.(*actionHandle)
+}
+
+func (s *snapshot) addActionHandle(ah *actionHandle, release func()) *actionHandle {
key := actionKey{
analyzer: ah.analyzer,
pkg: packageKey{
@@ -1201,10 +1206,17 @@ func (s *snapshot) addActionHandle(ah *actionHandle) *actionHandle {
mode: ah.pkg.mode,
},
}
- if ah, ok := s.actions[key]; ok {
- return ah
+
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ // If another thread since cached a different handle,
+ // return it instead of overriding it.
+ if result, ok := s.actions.Get(key); ok {
+ release()
+ return result.(*actionHandle)
}
- s.actions[key] = ah
+ s.actions.Set(key, ah, func(_, _ interface{}) { release() })
return ah
}
@@ -1716,7 +1728,7 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
initializedErr: s.initializedErr,
packages: s.packages.Clone(),
isActivePackageCache: s.isActivePackageCache.Clone(),
- actions: make(map[actionKey]*actionHandle, len(s.actions)),
+ actions: s.actions.Clone(),
files: s.files.Clone(),
goFiles: s.goFiles.Clone(),
parseKeysByURI: s.parseKeysByURI.Clone(),
@@ -1920,13 +1932,17 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
}
}
- // Copy the package analysis information.
- for k, v := range s.actions {
- if _, ok := idsToInvalidate[k.pkg.id]; ok {
- continue
+ // Copy actions.
+ // TODO(adonovan): opt: avoid iteration over s.actions.
+ var actionsToDelete []actionKey
+ s.actions.Range(func(k, _ interface{}) {
+ key := k.(actionKey)
+ if _, ok := idsToInvalidate[key.pkg.id]; ok {
+ actionsToDelete = append(actionsToDelete, key)
}
- newGen.Inherit(v.handle)
- result.actions[k] = v
+ })
+ for _, key := range actionsToDelete {
+ result.actions.Delete(key)
}
// If the workspace mode has changed, we must delete all metadata, as it