Skip to content

Commit e435455

Browse files
committed
internal/lsp: introduce MemoryMode
We still hear from users for whom gopls uses too much memory. My efforts to reduce memory usage while maintaining functionality are proving fruitless, so perhaps it's time to accept some functionality loss. DegradeClosed MemoryMode typechecks all packages in ParseExported mode unless they have a file open. This should dramatically reduce memory usage in monorepo-style scenarious, where a ton of packages are in the workspace and the user might plausibly want to edit any of them. (Otherwise they should consider using directory filters.) The cost is that features that work across multiple packages...won't. Find references, for example, will only find uses in open packages or in the exported declarations of closed packages. The current implementation is a bit leaky; we keep the ParseFull packages in memory even once all their files are closed. This is related to a general failure on our part to drop unused packages from the snapshot, so I'm not going to try to fix it here. Updates golang/go#45457, golang/go#45363. Change-Id: I38b2aeeff81a1118024aed16a3b75e18f17893e2 Reviewed-on: https://go-review.googlesource.com/c/tools/+/310170 Trust: Heschi Kreinick <[email protected]> Run-TryBot: Heschi Kreinick <[email protected]> gopls-CI: kokoro <[email protected]> Reviewed-by: Robert Findley <[email protected]> TryBot-Result: Go Bot <[email protected]>
1 parent f7e8e24 commit e435455

File tree

6 files changed

+90
-7
lines changed

6 files changed

+90
-7
lines changed

gopls/doc/settings.md

+18
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,24 @@ Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-pro
7272

7373
Default: `[]`.
7474

75+
#### **memoryMode** *enum*
76+
77+
**This setting is experimental and may be deleted.**
78+
79+
memoryMode controls the tradeoff `gopls` makes between memory usage and
80+
correctness.
81+
82+
Values other than `Normal` are untested and may break in surprising ways.
83+
84+
Must be one of:
85+
86+
* `"DegradeClosed"`: In DegradeClosed mode, `gopls` will collect less information about
87+
packages without open files. As a result, features like Find
88+
References and Rename will miss results in such packages.
89+
90+
* `"Normal"`
91+
Default: `"Normal"`.
92+
7593
#### **expandWorkspaceToModule** *bool*
7694

7795
**This setting is experimental and may be deleted.**

internal/lsp/cache/check.go

+20-4
Original file line numberDiff line numberDiff line change
@@ -187,11 +187,27 @@ func (s *snapshot) buildKey(ctx context.Context, id packageID, mode source.Parse
187187
}
188188

189189
func (s *snapshot) workspaceParseMode(id packageID) source.ParseMode {
190-
if _, ws := s.isWorkspacePackage(id); ws {
190+
s.mu.Lock()
191+
defer s.mu.Unlock()
192+
_, ws := s.workspacePackages[id]
193+
if !ws {
194+
return source.ParseExported
195+
}
196+
if s.view.Options().MemoryMode == source.ModeNormal {
191197
return source.ParseFull
192-
} else {
198+
}
199+
200+
// Degraded mode. Check for open files.
201+
m, ok := s.metadata[id]
202+
if !ok {
193203
return source.ParseExported
194204
}
205+
for _, cgf := range m.compiledGoFiles {
206+
if s.isOpenLocked(cgf) {
207+
return source.ParseFull
208+
}
209+
}
210+
return source.ParseExported
195211
}
196212

197213
func checkPackageKey(id packageID, pghs []*parseGoHandle, cfg *packages.Config, deps []packageHandleKey, mode source.ParseMode, experimentalKey bool) packageHandleKey {
@@ -547,7 +563,7 @@ func (s *snapshot) depsErrors(ctx context.Context, pkg *pkg) ([]*source.Diagnost
547563
}
548564

549565
directImporter := depsError.ImportStack[directImporterIdx]
550-
if _, ok := s.isWorkspacePackage(packageID(directImporter)); ok {
566+
if s.isWorkspacePackage(packageID(directImporter)) {
551567
continue
552568
}
553569
relevantErrors = append(relevantErrors, depsError)
@@ -582,7 +598,7 @@ func (s *snapshot) depsErrors(ctx context.Context, pkg *pkg) ([]*source.Diagnost
582598
for _, depErr := range relevantErrors {
583599
for i := len(depErr.ImportStack) - 1; i >= 0; i-- {
584600
item := depErr.ImportStack[i]
585-
if _, ok := s.isWorkspacePackage(packageID(item)); ok {
601+
if s.isWorkspacePackage(packageID(item)) {
586602
break
587603
}
588604

internal/lsp/cache/snapshot.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -963,12 +963,12 @@ func isCommandLineArguments(s string) bool {
963963
return strings.Contains(s, "command-line-arguments")
964964
}
965965

966-
func (s *snapshot) isWorkspacePackage(id packageID) (packagePath, bool) {
966+
func (s *snapshot) isWorkspacePackage(id packageID) bool {
967967
s.mu.Lock()
968968
defer s.mu.Unlock()
969969

970-
scope, ok := s.workspacePackages[id]
971-
return scope, ok
970+
_, ok := s.workspacePackages[id]
971+
return ok
972972
}
973973

974974
func (s *snapshot) FindFile(uri span.URI) source.VersionedFileHandle {

internal/lsp/cache/view.go

+3
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,9 @@ func minorOptionsChange(a, b *source.Options) bool {
254254
if !reflect.DeepEqual(a.DirectoryFilters, b.DirectoryFilters) {
255255
return false
256256
}
257+
if a.MemoryMode != b.MemoryMode {
258+
return false
259+
}
257260
aBuildFlags := make([]string, len(a.BuildFlags))
258261
bBuildFlags := make([]string, len(b.BuildFlags))
259262
copy(aBuildFlags, a.BuildFlags)

internal/lsp/source/api_json.go

+22
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

internal/lsp/source/options.go

+24
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ func DefaultOptions() *Options {
107107
BuildOptions: BuildOptions{
108108
ExpandWorkspaceToModule: true,
109109
ExperimentalPackageCacheKey: true,
110+
MemoryMode: ModeNormal,
110111
},
111112
UIOptions: UIOptions{
112113
DiagnosticOptions: DiagnosticOptions{
@@ -221,6 +222,12 @@ type BuildOptions struct {
221222
// Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules`
222223
DirectoryFilters []string
223224

225+
// MemoryMode controls the tradeoff `gopls` makes between memory usage and
226+
// correctness.
227+
//
228+
// Values other than `Normal` are untested and may break in surprising ways.
229+
MemoryMode MemoryMode `status:"experimental"`
230+
224231
// ExpandWorkspaceToModule instructs `gopls` to adjust the scope of the
225232
// workspace to find the best available module root. `gopls` first looks for
226233
// a go.mod file in any parent directory of the workspace folder, expanding
@@ -550,6 +557,16 @@ const (
550557
Structured HoverKind = "Structured"
551558
)
552559

560+
type MemoryMode string
561+
562+
const (
563+
ModeNormal MemoryMode = "Normal"
564+
// In DegradeClosed mode, `gopls` will collect less information about
565+
// packages without open files. As a result, features like Find
566+
// References and Rename will miss results in such packages.
567+
ModeDegradeClosed MemoryMode = "DegradeClosed"
568+
)
569+
553570
type OptionResults []OptionResult
554571

555572
type OptionResult struct {
@@ -753,6 +770,13 @@ func (o *Options) set(name string, value interface{}, seen map[string]struct{})
753770
filters = append(filters, strings.TrimRight(filepath.FromSlash(filter), "/"))
754771
}
755772
o.DirectoryFilters = filters
773+
case "memoryMode":
774+
if s, ok := result.asOneOf(
775+
string(ModeNormal),
776+
string(ModeDegradeClosed),
777+
); ok {
778+
o.MemoryMode = MemoryMode(s)
779+
}
756780
case "completionDocumentation":
757781
result.setBool(&o.CompletionDocumentation)
758782
case "usePlaceholders":

0 commit comments

Comments
 (0)