-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
gopls/internal/lsp/cache: record parse keys when they're created
When we parse a file through snapshot.ParseGo, we must record the parse key by its URI for later invalidation. This wasn't done, resulting in a memory leak. Fix the leak by actually recording parse keys in the parseKeysByURI map, which was previously always empty. Write a test that diffs the cache before and after a change, which would have caught this bug (and others). Fixes golang/go#57222 Change-Id: I308812bf1030276dff08c26d359433750f44849a Reviewed-on: https://go-review.googlesource.com/c/tools/+/456642 Reviewed-by: Alan Donovan <adonovan@google.com> Run-TryBot: Robert Findley <rfindley@google.com> TryBot-Result: Gopher Robot <gobot@golang.org> gopls-CI: kokoro <noreply+kokoro@google.com>
- Loading branch information
Showing
2 changed files
with
106 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// Copyright 2022 The Go Authors. All rights reserved. | ||
// Use of this source code is governed by a BSD-style | ||
// license that can be found in the LICENSE file. | ||
|
||
package misc | ||
|
||
import ( | ||
"context" | ||
"testing" | ||
|
||
"github.com/google/go-cmp/cmp" | ||
"golang.org/x/tools/gopls/internal/hooks" | ||
"golang.org/x/tools/gopls/internal/lsp/cache" | ||
"golang.org/x/tools/gopls/internal/lsp/debug" | ||
"golang.org/x/tools/gopls/internal/lsp/fake" | ||
"golang.org/x/tools/gopls/internal/lsp/lsprpc" | ||
. "golang.org/x/tools/gopls/internal/lsp/regtest" | ||
"golang.org/x/tools/internal/jsonrpc2" | ||
"golang.org/x/tools/internal/jsonrpc2/servertest" | ||
) | ||
|
||
// Test for golang/go#57222. | ||
func TestCacheLeak(t *testing.T) { | ||
const files = `-- a.go -- | ||
package a | ||
func _() { | ||
println("1") | ||
} | ||
` | ||
c := cache.New(nil, nil) | ||
env := setupEnv(t, files, c) | ||
env.Await(InitialWorkspaceLoad) | ||
env.OpenFile("a.go") | ||
|
||
// Make a couple edits to stabilize cache state. | ||
// | ||
// For some reason, after only one edit we're left with two parsed files | ||
// (perhaps because something had to ParseHeader). If this test proves flaky, | ||
// we'll need to investigate exactly what is causing various parse modes to | ||
// be present (or rewrite the test to be more tolerant, for example make ~100 | ||
// modifications and assert that we're within a few of where we're started). | ||
env.RegexpReplace("a.go", "1", "2") | ||
env.RegexpReplace("a.go", "2", "3") | ||
env.AfterChange() | ||
|
||
// Capture cache state, make an arbitrary change, and wait for gopls to do | ||
// its work. Afterward, we should have the exact same number of parsed | ||
before := c.MemStats() | ||
env.RegexpReplace("a.go", "3", "4") | ||
env.AfterChange() | ||
after := c.MemStats() | ||
|
||
if diff := cmp.Diff(before, after); diff != "" { | ||
t.Errorf("store objects differ after change (-before +after)\n%s", diff) | ||
} | ||
} | ||
|
||
// setupEnv creates a new sandbox environment for editing the txtar encoded | ||
// content of files. It uses a new gopls instance backed by the Cache c. | ||
func setupEnv(t *testing.T, files string, c *cache.Cache) *Env { | ||
ctx := debug.WithInstance(context.Background(), "", "off") | ||
server := lsprpc.NewStreamServer(c, false, hooks.Options) | ||
ts := servertest.NewPipeServer(server, jsonrpc2.NewRawStream) | ||
s, err := fake.NewSandbox(&fake.SandboxConfig{ | ||
Files: fake.UnpackTxt(files), | ||
}) | ||
if err != nil { | ||
t.Fatal(err) | ||
} | ||
|
||
a := NewAwaiter(s.Workdir) | ||
e, err := fake.NewEditor(s, fake.EditorConfig{}).Connect(ctx, ts, a.Hooks()) | ||
|
||
return &Env{ | ||
T: t, | ||
Ctx: ctx, | ||
Editor: e, | ||
Sandbox: s, | ||
Awaiter: a, | ||
} | ||
} |