From 4fc0492b8eca8657dd80eff56bf885b7258ddd73 Mon Sep 17 00:00:00 2001 From: Rob Findley Date: Tue, 20 Oct 2020 12:58:16 -0400 Subject: [PATCH] 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 gopls-CI: kokoro TryBot-Result: Go Bot Reviewed-by: Heschi Kreinick Reviewed-by: Rebecca Stambler Trust: Robert Findley --- internal/lsp/cache/load.go | 70 +++++++++++++++++++++++----------- internal/lsp/cache/snapshot.go | 10 ++++- 2 files changed, 56 insertions(+), 24 deletions(-) diff --git a/internal/lsp/cache/load.go b/internal/lsp/cache/load.go index f5bb720197..ccf0c1a162 100644 --- a/internal/lsp/cache/load.go +++ b/internal/lsp/cache/load.go @@ -19,6 +19,7 @@ import ( "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 @@ func (s *snapshot) parseLoadError(ctx context.Context, loadErr error) *source.Er 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 e452df193c..83bb6652fe 100644 --- a/internal/lsp/cache/snapshot.go +++ b/internal/lsp/cache/snapshot.go @@ -92,7 +92,8 @@ type snapshot struct { modUpgradeHandles map[span.URI]*modUpgradeHandle modWhyHandles map[span.URI]*modWhyHandle - workspace *workspace + workspace *workspace + workspaceDirHandle *memoize.Handle } type packageKey struct { @@ -252,7 +253,7 @@ func (s *snapshot) goCommandInvocation(ctx context.Context, mode source.Invocati } 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 @@ func (s *snapshot) clone(ctx context.Context, changes map[span.URI]*fileChange, workspace: newWorkspace, } + if !workspaceChanged && s.workspaceDirHandle != nil { + result.workspaceDirHandle = s.workspaceDirHandle + newGen.Inherit(s.workspaceDirHandle) + } + if s.builtin != nil { newGen.Inherit(s.builtin.handle) }