Skip to content

Commit 67f59a0

Browse files
findleyrgopherbot
authored andcommitted
gopls/internal/lsp/cache: don't rebuild filters for each modcache dir
While investigating a post-review comment on CL 538796, I observed that the importsState.SkipPathInScan func is building a directory filterer for every call. This can be very expensive, and may partially explain the performance problems encountered in golang/go#59216. Fix this, and update the test to catch bugs related to mixing up relpath vs abspath (a mistake I made during this CL). Updates golang/go#59216 Change-Id: I696429161ba039a380df1cf258edf78369acb398 Reviewed-on: https://go-review.googlesource.com/c/tools/+/544515 LUCI-TryBot-Result: Go LUCI <[email protected]> Reviewed-by: Alan Donovan <[email protected]> Auto-Submit: Robert Findley <[email protected]>
1 parent a9ef4cf commit 67f59a0

File tree

4 files changed

+57
-40
lines changed

4 files changed

+57
-40
lines changed

gopls/internal/lsp/cache/session.go

Lines changed: 32 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,32 @@ func (s *Session) createView(ctx context.Context, def *viewDefinition, folder *F
122122
baseCtx := event.Detach(xcontext.Detach(ctx))
123123
backgroundCtx, cancel := context.WithCancel(baseCtx)
124124

125+
// Compute a skip function to use for module cache scanning.
126+
//
127+
// Note that unlike other filtering operations, we definitely don't want to
128+
// exclude the gomodcache here, even if it is contained in the workspace
129+
// folder.
130+
//
131+
// TODO(rfindley): consolidate with relPathExcludedByFilter(Func), Filterer,
132+
// View.filterFunc.
133+
var skipPath func(string) bool
134+
{
135+
// Compute a prefix match, respecting segment boundaries, by ensuring
136+
// the pattern (dir) has a trailing slash.
137+
dirPrefix := strings.TrimSuffix(string(folder.Dir), "/") + "/"
138+
filterer := NewFilterer(folder.Options.DirectoryFilters)
139+
skipPath = func(dir string) bool {
140+
uri := strings.TrimSuffix(string(protocol.URIFromPath(dir)), "/")
141+
// Note that the logic below doesn't handle the case where uri ==
142+
// v.folder.Dir, because there is no point in excluding the entire
143+
// workspace folder!
144+
if rel := strings.TrimPrefix(uri, dirPrefix); rel != uri {
145+
return filterer.Disallow(rel)
146+
}
147+
return false
148+
}
149+
}
150+
125151
v := &View{
126152
id: strconv.FormatInt(index, 10),
127153
gocmdRunner: s.gocmdRunner,
@@ -132,24 +158,15 @@ func (s *Session) createView(ctx context.Context, def *viewDefinition, folder *F
132158
parseCache: s.parseCache,
133159
fs: s.overlayFS,
134160
viewDefinition: def,
135-
}
136-
v.importsState = &importsState{
137-
ctx: backgroundCtx,
138-
processEnv: &imports.ProcessEnv{
139-
GocmdRunner: s.gocmdRunner,
140-
SkipPathInScan: func(dir string) bool {
141-
prefix := strings.TrimSuffix(string(v.folder.Dir), "/") + "/"
142-
uri := strings.TrimSuffix(string(protocol.URIFromPath(dir)), "/")
143-
if !strings.HasPrefix(uri+"/", prefix) {
144-
return false
145-
}
146-
filterer := NewFilterer(folder.Options.DirectoryFilters)
147-
rel := strings.TrimPrefix(uri, prefix)
148-
disallow := filterer.Disallow(rel)
149-
return disallow
161+
importsState: &importsState{
162+
ctx: backgroundCtx,
163+
processEnv: &imports.ProcessEnv{
164+
GocmdRunner: s.gocmdRunner,
165+
SkipPathInScan: skipPath,
150166
},
151167
},
152168
}
169+
153170
v.snapshot = &Snapshot{
154171
sequenceID: seqID,
155172
globalID: nextSnapshotID(),

gopls/internal/lsp/cache/view.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -585,11 +585,11 @@ func (v *View) contains(uri protocol.DocumentURI) bool {
585585
// directoryFilters.
586586
func (v *View) filterFunc() func(protocol.DocumentURI) bool {
587587
folderDir := v.folder.Dir.Path()
588-
filterer := buildFilterer(folderDir, v.gomodcache, v.folder.Options)
588+
filterer := buildFilterer(folderDir, v.gomodcache, v.folder.Options.DirectoryFilters)
589589
return func(uri protocol.DocumentURI) bool {
590590
// Only filter relative to the configured root directory.
591591
if pathutil.InDir(folderDir, uri.Path()) {
592-
return pathExcludedByFilter(strings.TrimPrefix(uri.Path(), folderDir), filterer)
592+
return relPathExcludedByFilter(strings.TrimPrefix(uri.Path(), folderDir), filterer)
593593
}
594594
return false
595595
}
@@ -971,7 +971,7 @@ func getViewDefinition(ctx context.Context, runner *gocommand.Runner, fs file.So
971971

972972
// filterFunc is the path filter function for this workspace folder. Notably,
973973
// it is relative to folder (which is specified by the user), not root.
974-
filterFunc := pathExcludedByFilterFunc(folder.Dir.Path(), def.gomodcache, folder.Options)
974+
filterFunc := relPathExcludedByFilterFunc(folder.Dir.Path(), def.gomodcache, folder.Options.DirectoryFilters)
975975
def.gomod, err = findWorkspaceModFile(ctx, folder.Dir, fs, filterFunc)
976976
if err != nil {
977977
return nil, err
@@ -1197,6 +1197,8 @@ var modFlagRegexp = regexp.MustCompile(`-mod[ =](\w+)`)
11971197
// after we have a version of the workspace go.mod file on disk. Getting a
11981198
// FileHandle from the cache for temporary files is problematic, since we
11991199
// cannot delete it.
1200+
//
1201+
// TODO(rfindley): move this to snapshot.go.
12001202
func (s *Snapshot) vendorEnabled(ctx context.Context, modURI protocol.DocumentURI, modContent []byte) (bool, error) {
12011203
// Legacy GOPATH workspace?
12021204
if len(s.view.workspaceModFiles) == 0 {
@@ -1244,27 +1246,26 @@ func allFilesExcluded(files []string, filterFunc func(protocol.DocumentURI) bool
12441246
return true
12451247
}
12461248

1247-
func pathExcludedByFilterFunc(folder, gomodcache string, opts *settings.Options) func(string) bool {
1248-
filterer := buildFilterer(folder, gomodcache, opts)
1249+
// relPathExcludedByFilterFunc returns a func that filters paths relative to the
1250+
// given folder according the given GOMODCACHE value and directory filters (see
1251+
// settings.BuildOptions.DirectoryFilters).
1252+
//
1253+
// The resulting func returns true if the directory should be skipped.
1254+
func relPathExcludedByFilterFunc(folder, gomodcache string, directoryFilters []string) func(string) bool {
1255+
filterer := buildFilterer(folder, gomodcache, directoryFilters)
12491256
return func(path string) bool {
1250-
return pathExcludedByFilter(path, filterer)
1257+
return relPathExcludedByFilter(path, filterer)
12511258
}
12521259
}
12531260

1254-
// pathExcludedByFilter reports whether the path (relative to the workspace
1255-
// folder) should be excluded by the configured directory filters.
1256-
//
1257-
// TODO(rfindley): passing root and gomodcache here makes it confusing whether
1258-
// path should be absolute or relative, and has already caused at least one
1259-
// bug.
1260-
func pathExcludedByFilter(path string, filterer *Filterer) bool {
1261+
func relPathExcludedByFilter(path string, filterer *Filterer) bool {
12611262
path = strings.TrimPrefix(filepath.ToSlash(path), "/")
12621263
return filterer.Disallow(path)
12631264
}
12641265

1265-
func buildFilterer(folder, gomodcache string, opts *settings.Options) *Filterer {
1266-
filters := opts.DirectoryFilters
1267-
1266+
func buildFilterer(folder, gomodcache string, directoryFilters []string) *Filterer {
1267+
var filters []string
1268+
filters = append(filters, directoryFilters...)
12681269
if pref := strings.TrimPrefix(gomodcache, folder); pref != gomodcache {
12691270
modcacheFilter := "-" + strings.TrimPrefix(filepath.ToSlash(pref), "/")
12701271
filters = append(filters, modcacheFilter)

gopls/internal/lsp/cache/view_test.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -152,12 +152,12 @@ func TestFilters(t *testing.T) {
152152
for _, tt := range tests {
153153
filterer := NewFilterer(tt.filters)
154154
for _, inc := range tt.included {
155-
if pathExcludedByFilter(inc, filterer) {
155+
if relPathExcludedByFilter(inc, filterer) {
156156
t.Errorf("filters %q excluded %v, wanted included", tt.filters, inc)
157157
}
158158
}
159159
for _, exc := range tt.excluded {
160-
if !pathExcludedByFilter(exc, filterer) {
160+
if !relPathExcludedByFilter(exc, filterer) {
161161
t.Errorf("filters %q included %v, wanted excluded", tt.filters, exc)
162162
}
163163
}

gopls/internal/regtest/workspace/directoryfilters_test.go

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -171,23 +171,22 @@ package main
171171
172172
func main() {
173173
bye.Goodbye()
174+
hi.Hello()
174175
}
175176
-- p/bye/bye.go --
176177
package bye
177178
178179
func Goodbye() {}
180+
-- hi/hi.go --
181+
package hi
182+
183+
func Hello() {}
179184
`
180185

181186
WithOptions(
182187
Settings{
183-
"directoryFilters": []string{"-**/bye"},
188+
"directoryFilters": []string{"-**/bye", "-hi"},
184189
},
185-
// This test breaks in 'Experimental' mode, because with
186-
// experimentalWorkspaceModule set we the goimports scan behaves
187-
// differently.
188-
//
189-
// Since this feature is going away (golang/go#52897), don't investigate.
190-
Modes(Default),
191190
).Run(t, files, func(t *testing.T, env *Env) {
192191
env.OpenFile("main.go")
193192
beforeSave := env.BufferText("main.go")

0 commit comments

Comments
 (0)