internal/lsp/cache: keep a cached workspace module dir
Keep a workspace module dir around for running the go command against a
snapshot, bound to the contents of the workspace modfile.
This uses the cache's resource model to share the workspace module dir
across snapshots if it is not invalidated, and to delete it when it is
no longer in-use by a snapshot. Of course, the go command will still
only see files on the filesystem, but using this immutable model was
most consistent with the immutable workspace.
For golang/go#41836
Change-Id: Iaec544283b2f545071e5cab1d0ff2a66e6d24dff
Reviewed-on: https://go-review.googlesource.com/c/tools/+/263938
Run-TryBot: Robert Findley <rfindley@google.com>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
Reviewed-by: Rebecca Stambler <rstambler@golang.org>
Trust: Robert Findley <rfindley@google.com>
diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go
index f5bb720..ccf0c1a 100644
--- a/internal/lsp/cache/load.go
+++ b/internal/lsp/cache/load.go
@@ -19,6 +19,7 @@
"golang.org/x/tools/internal/gocommand"
"golang.org/x/tools/internal/lsp/debug/tag"
"golang.org/x/tools/internal/lsp/source"
+ "golang.org/x/tools/internal/memoize"
"golang.org/x/tools/internal/packagesinternal"
"golang.org/x/tools/internal/span"
errors "golang.org/x/xerrors"
@@ -184,37 +185,62 @@
return srcErrs
}
-// tempWorkspaceModule creates a temporary directory for use with
-// packages.Loads that occur from within the workspace module.
-func (s *snapshot) tempWorkspaceModule(ctx context.Context) (_ span.URI, cleanup func(), err error) {
- cleanup = func() {}
- if s.workspaceMode()&usesWorkspaceModule == 0 {
- return "", cleanup, nil
+type workspaceDirKey string
+
+type workspaceDirData struct {
+ dir string
+ err error
+}
+
+// getWorkspaceDir gets the URI for the workspace directory associated with
+// this snapshot. The workspace directory is a temp directory containing the
+// go.mod file computed from all active modules.
+func (s *snapshot) getWorkspaceDir(ctx context.Context) (span.URI, error) {
+ s.mu.Lock()
+ h := s.workspaceDirHandle
+ s.mu.Unlock()
+ if h != nil {
+ return getWorkspaceDir(ctx, h, s.generation)
}
file, err := s.workspace.modFile(ctx, s)
if err != nil {
- return "", nil, err
+ return "", err
}
-
content, err := file.Format()
if err != nil {
- return "", cleanup, err
+ return "", err
}
- // Create a temporary working directory for the go command that contains
- // the workspace module file.
- name, err := ioutil.TempDir("", "gopls-mod")
+ key := workspaceDirKey(hashContents(content))
+ s.mu.Lock()
+ s.workspaceDirHandle = s.generation.Bind(key, func(context.Context, memoize.Arg) interface{} {
+ tmpdir, err := ioutil.TempDir("", "gopls-workspace-mod")
+ if err != nil {
+ return &workspaceDirData{err: err}
+ }
+ filename := filepath.Join(tmpdir, "go.mod")
+ if err := ioutil.WriteFile(filename, content, 0644); err != nil {
+ os.RemoveAll(tmpdir)
+ return &workspaceDirData{err: err}
+ }
+ return &workspaceDirData{dir: tmpdir}
+ }, func(v interface{}) {
+ d := v.(*workspaceDirData)
+ if d.dir != "" {
+ if err := os.RemoveAll(d.dir); err != nil {
+ event.Error(context.Background(), "cleaning workspace dir", err)
+ }
+ }
+ })
+ s.mu.Unlock()
+ return getWorkspaceDir(ctx, s.workspaceDirHandle, s.generation)
+}
+
+func getWorkspaceDir(ctx context.Context, h *memoize.Handle, g *memoize.Generation) (span.URI, error) {
+ v, err := h.Get(ctx, g, nil)
if err != nil {
- return "", cleanup, err
+ return "", err
}
- cleanup = func() {
- os.RemoveAll(name)
- }
- filename := filepath.Join(name, "go.mod")
- if err := ioutil.WriteFile(filename, content, 0644); err != nil {
- cleanup()
- return "", cleanup, err
- }
- return span.URIFromPath(filepath.Dir(filename)), cleanup, nil
+ return span.URIFromPath(v.(*workspaceDirData).dir), nil
}
// setMetadata extracts metadata from pkg and records it in s. It
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index e452df1..83bb665 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -92,7 +92,8 @@
modUpgradeHandles map[span.URI]*modUpgradeHandle
modWhyHandles map[span.URI]*modWhyHandle
- workspace *workspace
+ workspace *workspace
+ workspaceDirHandle *memoize.Handle
}
type packageKey struct {
@@ -252,7 +253,7 @@
} else {
var tmpDir span.URI
var err error
- tmpDir, cleanup, err = s.tempWorkspaceModule(ctx)
+ tmpDir, err = s.getWorkspaceDir(ctx)
if err != nil {
return "", nil, cleanup, err
}
@@ -1001,6 +1002,11 @@
workspace: newWorkspace,
}
+ if !workspaceChanged && s.workspaceDirHandle != nil {
+ result.workspaceDirHandle = s.workspaceDirHandle
+ newGen.Inherit(s.workspaceDirHandle)
+ }
+
if s.builtin != nil {
newGen.Inherit(s.builtin.handle)
}