Skip to content

Commit f157068

Browse files
committed
internal/lsp/regtest: allow sharing memoized results across regtests
Each regtest does a significant amount of extra work re-doing things like parsing and type-checking the runtime package. We can share this work across regtests by using a shared cache, significantly speeding them up at the cost of potentially hiding bugs related to timing. Sharing this work still retains most of the benefit of the regtests, so implement this in the default mode (formerly called "singleton" and now renamed to "default"). In a subsequent CL, modes will be cleaned up so that "default" is the only mode that runs with -short. Making this change actually revealed a caching bug: our cached package stores error messages extracted from go/packages errors, but does not include these errors in the cache key. Fix this by hashing all metadata errors into the package cache key. Updates golang/go#39384 Change-Id: I37ab9604149d34c9a79fc02b0e1bc23fcb17c454 Reviewed-on: https://go-review.googlesource.com/c/tools/+/417587 TryBot-Result: Gopher Robot <[email protected]> gopls-CI: kokoro <[email protected]> Run-TryBot: Robert Findley <[email protected]> Reviewed-by: Bryan Mills <[email protected]>
1 parent 8ccb25c commit f157068

File tree

22 files changed

+182
-80
lines changed

22 files changed

+182
-80
lines changed

gopls/internal/regtest/bench/bench_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ func benchmarkOptions(dir string) []RunOption {
3333
// Skip logs as they buffer up memory unnaturally.
3434
SkipLogs(),
3535
// The Debug server only makes sense if running in singleton mode.
36-
Modes(Singleton),
36+
Modes(Default),
3737
// Remove the default timeout. Individual tests should control their
3838
// own graceful termination.
3939
NoDefaultTimeout(),

gopls/internal/regtest/debug/debug_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ func TestBugNotification(t *testing.T) {
2020
// Verify that a properly configured session gets notified of a bug on the
2121
// server.
2222
WithOptions(
23-
Modes(Singleton), // must be in-process to receive the bug report below
23+
Modes(Default), // must be in-process to receive the bug report below
2424
Settings{"showBugReports": true},
2525
).Run(t, "", func(t *testing.T, env *Env) {
2626
const desc = "got a bug"

gopls/internal/regtest/diagnostics/diagnostics_test.go

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ func Hello() {
298298

299299
t.Run("without workspace module", func(t *testing.T) {
300300
WithOptions(
301-
Modes(Singleton),
301+
Modes(Default),
302302
).Run(t, noMod, func(t *testing.T, env *Env) {
303303
env.Await(
304304
env.DiagnosticAtRegexp("main.go", `"mod.com/bob"`),
@@ -1678,7 +1678,7 @@ import (
16781678
WithOptions(
16791679
InGOPATH(),
16801680
EnvVars{"GO111MODULE": "off"},
1681-
Modes(Singleton),
1681+
Modes(Default),
16821682
).Run(t, mod, func(t *testing.T, env *Env) {
16831683
env.Await(
16841684
env.DiagnosticAtRegexpWithMessage("main.go", `"nosuchpkg"`, `cannot find package "nosuchpkg" in any of`),
@@ -1705,7 +1705,7 @@ package b
17051705
for _, go111module := range []string{"on", "auto"} {
17061706
t.Run("GO111MODULE="+go111module, func(t *testing.T) {
17071707
WithOptions(
1708-
Modes(Singleton),
1708+
Modes(Default),
17091709
EnvVars{"GO111MODULE": go111module},
17101710
).Run(t, modules, func(t *testing.T, env *Env) {
17111711
env.OpenFile("a/a.go")
@@ -1722,7 +1722,7 @@ package b
17221722
// Expect no warning if GO111MODULE=auto in a directory in GOPATH.
17231723
t.Run("GOPATH_GO111MODULE_auto", func(t *testing.T) {
17241724
WithOptions(
1725-
Modes(Singleton),
1725+
Modes(Default),
17261726
EnvVars{"GO111MODULE": "auto"},
17271727
InGOPATH(),
17281728
).Run(t, modules, func(t *testing.T, env *Env) {
@@ -1784,7 +1784,7 @@ func helloHelper() {}
17841784
`
17851785
WithOptions(
17861786
ProxyFiles(proxy),
1787-
Modes(Singleton),
1787+
Modes(Default),
17881788
).Run(t, nested, func(t *testing.T, env *Env) {
17891789
// Expect a diagnostic in a nested module.
17901790
env.OpenFile("nested/hello/hello.go")
@@ -1996,7 +1996,7 @@ func Hello() {}
19961996
`
19971997
WithOptions(
19981998
Settings{"experimentalUseInvalidMetadata": true},
1999-
Modes(Singleton),
1999+
Modes(Default),
20002000
).Run(t, mod, func(t *testing.T, env *Env) {
20012001
env.OpenFile("go.mod")
20022002
env.RegexpReplace("go.mod", "module mod.com", "modul mod.com") // break the go.mod file
@@ -2052,7 +2052,7 @@ func _() {}
20522052
Settings{"experimentalUseInvalidMetadata": true},
20532053
// ExperimentalWorkspaceModule has a different failure mode for this
20542054
// case.
2055-
Modes(Singleton),
2055+
Modes(Default),
20562056
).Run(t, mod, func(t *testing.T, env *Env) {
20572057
env.Await(
20582058
OnceMet(

gopls/internal/regtest/misc/semantictokens_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ func main() {}
2525
2626
`
2727
WithOptions(
28-
Modes(Singleton),
28+
Modes(Default),
2929
Settings{"allExperiments": true},
3030
).Run(t, src, func(t *testing.T, env *Env) {
3131
params := &protocol.SemanticTokensParams{}

gopls/internal/regtest/misc/vendor_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ func _() {
4949
}
5050
`
5151
WithOptions(
52-
Modes(Singleton),
52+
Modes(Default),
5353
ProxyFiles(basicProxy),
5454
).Run(t, pkgThatUsesVendoring, func(t *testing.T, env *Env) {
5555
env.OpenFile("a/a1.go")

gopls/internal/regtest/modfile/modfile_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -742,7 +742,7 @@ func main() {
742742
WithOptions(
743743
EnvVars{"GOFLAGS": "-mod=readonly"},
744744
ProxyFiles(proxy),
745-
Modes(Singleton),
745+
Modes(Default),
746746
).Run(t, mod, func(t *testing.T, env *Env) {
747747
env.OpenFile("main.go")
748748
original := env.ReadWorkspaceFile("go.mod")
@@ -922,7 +922,7 @@ func hello() {}
922922
// TODO(rFindley) this doesn't work in multi-module workspace mode, because
923923
// it keeps around the last parsing modfile. Update this test to also
924924
// exercise the workspace module.
925-
Modes(Singleton),
925+
Modes(Default),
926926
).Run(t, mod, func(t *testing.T, env *Env) {
927927
env.OpenFile("go.mod")
928928
env.Await(env.DoneWithOpen())
@@ -1090,7 +1090,7 @@ func main() {
10901090
`
10911091
WithOptions(
10921092
ProxyFiles(workspaceProxy),
1093-
Modes(Singleton),
1093+
Modes(Default),
10941094
).Run(t, mod, func(t *testing.T, env *Env) {
10951095
env.OpenFile("go.mod")
10961096
params := &protocol.PublishDiagnosticsParams{}
@@ -1159,7 +1159,7 @@ func main() {
11591159
`
11601160
WithOptions(
11611161
ProxyFiles(proxy),
1162-
Modes(Singleton),
1162+
Modes(Default),
11631163
).Run(t, mod, func(t *testing.T, env *Env) {
11641164
env.OpenFile("main.go")
11651165
d := &protocol.PublishDiagnosticsParams{}

gopls/internal/regtest/workspace/workspace_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1205,7 +1205,7 @@ package main
12051205
`
12061206
WithOptions(
12071207
EnvVars{"GOPATH": filepath.FromSlash("$SANDBOX_WORKDIR/gopath")},
1208-
Modes(Singleton),
1208+
Modes(Default),
12091209
).Run(t, mod, func(t *testing.T, env *Env) {
12101210
env.Await(
12111211
// Confirm that the build configuration is seen as valid,
@@ -1236,7 +1236,7 @@ package main
12361236
func main() {}
12371237
`
12381238
WithOptions(
1239-
Modes(Singleton),
1239+
Modes(Default),
12401240
).Run(t, nomod, func(t *testing.T, env *Env) {
12411241
env.OpenFile("a/main.go")
12421242
env.OpenFile("b/main.go")

gopls/internal/vulncheck/command_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -293,7 +293,7 @@ func runTest(t *testing.T, workspaceData, proxyData string, test func(context.Co
293293
t.Fatal(err)
294294
}
295295

296-
cache := cache.New(nil)
296+
cache := cache.New(nil, nil, nil)
297297
session := cache.NewSession(ctx)
298298
options := source.DefaultOptions().Clone()
299299
tests.DefaultOptions(options)

internal/lsp/cache/cache.go

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,23 +28,46 @@ import (
2828
"golang.org/x/tools/internal/span"
2929
)
3030

31-
func New(options func(*source.Options)) *Cache {
31+
// New Creates a new cache for gopls operation results, using the given file
32+
// set, shared store, and session options.
33+
//
34+
// All of the fset, store and options may be nil, but if store is non-nil so
35+
// must be fset (and they must always be used together), otherwise it may be
36+
// possible to get cached data referencing token.Pos values not mapped by the
37+
// FileSet.
38+
func New(fset *token.FileSet, store *memoize.Store, options func(*source.Options)) *Cache {
3239
index := atomic.AddInt64(&cacheIndex, 1)
40+
41+
if store != nil && fset == nil {
42+
panic("non-nil store with nil fset")
43+
}
44+
if fset == nil {
45+
fset = token.NewFileSet()
46+
}
47+
if store == nil {
48+
store = &memoize.Store{}
49+
}
50+
3351
c := &Cache{
3452
id: strconv.FormatInt(index, 10),
35-
fset: token.NewFileSet(),
53+
fset: fset,
3654
options: options,
55+
store: store,
3756
fileContent: map[span.URI]*fileHandle{},
3857
}
3958
return c
4059
}
4160

4261
type Cache struct {
43-
id string
44-
fset *token.FileSet
62+
id string
63+
fset *token.FileSet
64+
65+
// TODO(rfindley): it doesn't make sense that cache accepts LSP options, just
66+
// so that it can create a session: the cache does not (and should not)
67+
// depend on options. Invert this relationship to remove options from Cache.
4568
options func(*source.Options)
4669

47-
store memoize.Store
70+
store *memoize.Store
4871

4972
fileMu sync.Mutex
5073
fileContent map[span.URI]*fileHandle

internal/lsp/cache/check.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -249,6 +249,16 @@ func computePackageKey(id PackageID, files []source.FileHandle, m *KnownMetadata
249249
for _, file := range files {
250250
b.WriteString(file.FileIdentity().String())
251251
}
252+
// Metadata errors are interpreted and memoized on the computed package, so
253+
// we must hash them into the key here.
254+
//
255+
// TODO(rfindley): handle metadata diagnostics independently from
256+
// type-checking diagnostics.
257+
for _, err := range m.Errors {
258+
b.WriteString(err.Msg)
259+
b.WriteString(err.Pos)
260+
b.WriteRune(rune(err.Kind))
261+
}
252262
return packageHandleKey(source.HashOf(b.Bytes()))
253263
}
254264

0 commit comments

Comments
 (0)