Skip to content

Commit

Permalink
internal/lsp: introduce MemoryMode
Browse files Browse the repository at this point in the history
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 <heschi@google.com>
Run-TryBot: Heschi Kreinick <heschi@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
Reviewed-by: Robert Findley <rfindley@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
  • Loading branch information
heschi committed Apr 23, 2021
1 parent f7e8e24 commit e435455
Show file tree
Hide file tree
Showing 6 changed files with 90 additions and 7 deletions.
18 changes: 18 additions & 0 deletions gopls/doc/settings.md
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,24 @@ Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-pro

Default: `[]`.

#### **memoryMode** *enum*

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

memoryMode controls the tradeoff `gopls` makes between memory usage and
correctness.

Values other than `Normal` are untested and may break in surprising ways.

Must be one of:

* `"DegradeClosed"`: In DegradeClosed mode, `gopls` will collect less information about
packages without open files. As a result, features like Find
References and Rename will miss results in such packages.

* `"Normal"`
Default: `"Normal"`.

#### **expandWorkspaceToModule** *bool*

**This setting is experimental and may be deleted.**
Expand Down
24 changes: 20 additions & 4 deletions internal/lsp/cache/check.go
Original file line number Diff line number Diff line change
Expand Up @@ -187,11 +187,27 @@ func (s *snapshot) buildKey(ctx context.Context, id packageID, mode source.Parse
}

func (s *snapshot) workspaceParseMode(id packageID) source.ParseMode {
if _, ws := s.isWorkspacePackage(id); ws {
s.mu.Lock()
defer s.mu.Unlock()
_, ws := s.workspacePackages[id]
if !ws {
return source.ParseExported
}
if s.view.Options().MemoryMode == source.ModeNormal {
return source.ParseFull
} else {
}

// Degraded mode. Check for open files.
m, ok := s.metadata[id]
if !ok {
return source.ParseExported
}
for _, cgf := range m.compiledGoFiles {
if s.isOpenLocked(cgf) {
return source.ParseFull
}
}
return source.ParseExported
}

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

directImporter := depsError.ImportStack[directImporterIdx]
if _, ok := s.isWorkspacePackage(packageID(directImporter)); ok {
if s.isWorkspacePackage(packageID(directImporter)) {
continue
}
relevantErrors = append(relevantErrors, depsError)
Expand Down Expand Up @@ -582,7 +598,7 @@ func (s *snapshot) depsErrors(ctx context.Context, pkg *pkg) ([]*source.Diagnost
for _, depErr := range relevantErrors {
for i := len(depErr.ImportStack) - 1; i >= 0; i-- {
item := depErr.ImportStack[i]
if _, ok := s.isWorkspacePackage(packageID(item)); ok {
if s.isWorkspacePackage(packageID(item)) {
break
}

Expand Down
6 changes: 3 additions & 3 deletions internal/lsp/cache/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -963,12 +963,12 @@ func isCommandLineArguments(s string) bool {
return strings.Contains(s, "command-line-arguments")
}

func (s *snapshot) isWorkspacePackage(id packageID) (packagePath, bool) {
func (s *snapshot) isWorkspacePackage(id packageID) bool {
s.mu.Lock()
defer s.mu.Unlock()

scope, ok := s.workspacePackages[id]
return scope, ok
_, ok := s.workspacePackages[id]
return ok
}

func (s *snapshot) FindFile(uri span.URI) source.VersionedFileHandle {
Expand Down
3 changes: 3 additions & 0 deletions internal/lsp/cache/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,6 +254,9 @@ func minorOptionsChange(a, b *source.Options) bool {
if !reflect.DeepEqual(a.DirectoryFilters, b.DirectoryFilters) {
return false
}
if a.MemoryMode != b.MemoryMode {
return false
}
aBuildFlags := make([]string, len(a.BuildFlags))
bBuildFlags := make([]string, len(b.BuildFlags))
copy(aBuildFlags, a.BuildFlags)
Expand Down
22 changes: 22 additions & 0 deletions internal/lsp/source/api_json.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

24 changes: 24 additions & 0 deletions internal/lsp/source/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ func DefaultOptions() *Options {
BuildOptions: BuildOptions{
ExpandWorkspaceToModule: true,
ExperimentalPackageCacheKey: true,
MemoryMode: ModeNormal,
},
UIOptions: UIOptions{
DiagnosticOptions: DiagnosticOptions{
Expand Down Expand Up @@ -221,6 +222,12 @@ type BuildOptions struct {
// Include only project_a, but not node_modules inside it: `-`, `+project_a`, `-project_a/node_modules`
DirectoryFilters []string

// MemoryMode controls the tradeoff `gopls` makes between memory usage and
// correctness.
//
// Values other than `Normal` are untested and may break in surprising ways.
MemoryMode MemoryMode `status:"experimental"`

// ExpandWorkspaceToModule instructs `gopls` to adjust the scope of the
// workspace to find the best available module root. `gopls` first looks for
// a go.mod file in any parent directory of the workspace folder, expanding
Expand Down Expand Up @@ -550,6 +557,16 @@ const (
Structured HoverKind = "Structured"
)

type MemoryMode string

const (
ModeNormal MemoryMode = "Normal"
// In DegradeClosed mode, `gopls` will collect less information about
// packages without open files. As a result, features like Find
// References and Rename will miss results in such packages.
ModeDegradeClosed MemoryMode = "DegradeClosed"
)

type OptionResults []OptionResult

type OptionResult struct {
Expand Down Expand Up @@ -753,6 +770,13 @@ func (o *Options) set(name string, value interface{}, seen map[string]struct{})
filters = append(filters, strings.TrimRight(filepath.FromSlash(filter), "/"))
}
o.DirectoryFilters = filters
case "memoryMode":
if s, ok := result.asOneOf(
string(ModeNormal),
string(ModeDegradeClosed),
); ok {
o.MemoryMode = MemoryMode(s)
}
case "completionDocumentation":
result.setBool(&o.CompletionDocumentation)
case "usePlaceholders":
Expand Down

0 comments on commit e435455

Please sign in to comment.