internal/lsp: fix concurrency issues in view initialization
Address issues from CL 254940.
Change-Id: If4839a8694979fd1951b3fad77abb10860b42b7c
Reviewed-on: https://go-review.googlesource.com/c/tools/+/255349
Trust: Rebecca Stambler <rstambler@golang.org>
Run-TryBot: Rebecca Stambler <rstambler@golang.org>
gopls-CI: kokoro <noreply+kokoro@google.com>
TryBot-Result: Go Bot <gobot@golang.org>
Reviewed-by: Heschi Kreinick <heschi@google.com>
diff --git a/internal/lsp/cache/session.go b/internal/lsp/cache/session.go
index 070bc5f..28aae96 100644
--- a/internal/lsp/cache/session.go
+++ b/internal/lsp/cache/session.go
@@ -239,12 +239,13 @@
// Initialize the view without blocking.
initCtx, initCancel := context.WithCancel(xcontext.Detach(ctx))
v.initCancelFirstAttempt = initCancel
+ snapshot := v.snapshot
+ release := snapshot.generation.Acquire(initCtx)
go func() {
- release := v.snapshot.generation.Acquire(initCtx)
- v.initialize(initCtx, v.snapshot, true)
+ v.initialize(initCtx, snapshot, true)
release()
}()
- return v, v.snapshot, v.snapshot.generation.Acquire(ctx), nil
+ return v, snapshot, snapshot.generation.Acquire(ctx), nil
}
// findWorkspaceModules walks the view's root folder, looking for go.mod files.
diff --git a/internal/lsp/cache/snapshot.go b/internal/lsp/cache/snapshot.go
index 81d5fc9..ee553e6 100644
--- a/internal/lsp/cache/snapshot.go
+++ b/internal/lsp/cache/snapshot.go
@@ -526,7 +526,7 @@
func (s *snapshot) CachedImportPaths(ctx context.Context) (map[string]source.Package, error) {
// Don't reload workspace package metadata.
// This function is meant to only return currently cached information.
- s.view.AwaitInitialized(ctx)
+ s.AwaitInitialized(ctx)
s.mu.Lock()
defer s.mu.Unlock()
@@ -705,7 +705,7 @@
func (s *snapshot) awaitLoaded(ctx context.Context) error {
// Do not return results until the snapshot's view has been initialized.
- s.view.AwaitInitialized(ctx)
+ s.AwaitInitialized(ctx)
if err := s.reloadWorkspace(ctx); err != nil {
return err
@@ -724,6 +724,17 @@
return nil
}
+func (s *snapshot) AwaitInitialized(ctx context.Context) {
+ select {
+ case <-ctx.Done():
+ return
+ case <-s.view.initialized:
+ }
+ // We typically prefer to run something as intensive as the IWL without
+ // blocking. I'm not sure if there is a way to do that here.
+ s.view.initialize(ctx, s, false)
+}
+
// reloadWorkspace reloads the metadata for all invalidated workspace packages.
func (s *snapshot) reloadWorkspace(ctx context.Context) error {
// If the view's build configuration is invalid, we cannot reload by package path.
@@ -1202,7 +1213,7 @@
}
func (s *snapshot) BuiltinPackage(ctx context.Context) (*source.BuiltinPackage, error) {
- s.view.AwaitInitialized(ctx)
+ s.AwaitInitialized(ctx)
if s.builtin == nil {
return nil, errors.Errorf("no builtin package for view %s", s.view.name)
diff --git a/internal/lsp/cache/view.go b/internal/lsp/cache/view.go
index 2ed8d62..cc83f94 100644
--- a/internal/lsp/cache/view.go
+++ b/internal/lsp/cache/view.go
@@ -677,15 +677,9 @@
}
func (v *View) Snapshot(ctx context.Context) (source.Snapshot, func()) {
- s := v.getSnapshot()
- return s, s.generation.Acquire(ctx)
-}
-
-func (v *View) getSnapshot() *snapshot {
v.snapshotMu.Lock()
defer v.snapshotMu.Unlock()
-
- return v.snapshot
+ return v.snapshot, v.snapshot.generation.Acquire(ctx)
}
func (v *View) initialize(ctx context.Context, s *snapshot, firstAttempt bool) {
@@ -743,18 +737,6 @@
})
}
-// AwaitInitialized waits until a view is initialized
-func (v *View) AwaitInitialized(ctx context.Context) {
- select {
- case <-ctx.Done():
- return
- case <-v.initialized:
- }
- // We typically prefer to run something as intensive as the IWL without
- // blocking. I'm not sure if there is a way to do that here.
- v.initialize(ctx, v.getSnapshot(), false)
-}
-
// invalidateContent invalidates the content of a Go file,
// including any position and type information that depends on it.
// It returns true if we were already tracking the given file, false otherwise.
@@ -767,7 +749,7 @@
v.cancelBackground()
// Do not clone a snapshot until its view has finished initializing.
- v.AwaitInitialized(ctx)
+ v.snapshot.AwaitInitialized(ctx)
// This should be the only time we hold the view's snapshot lock for any period of time.
v.snapshotMu.Lock()
diff --git a/internal/lsp/general.go b/internal/lsp/general.go
index af868e7..78b8e0a 100644
--- a/internal/lsp/general.go
+++ b/internal/lsp/general.go
@@ -205,8 +205,11 @@
work.end(fmt.Sprintf("Error loading packages: %s", err))
continue
}
+ var swg sync.WaitGroup
+ swg.Add(1)
go func() {
- view.AwaitInitialized(ctx)
+ defer swg.Done()
+ snapshot.AwaitInitialized(ctx)
work.end("Finished loading packages.")
}()
@@ -226,6 +229,7 @@
wg.Add(1)
go func() {
s.diagnoseDetached(snapshot)
+ swg.Wait()
release()
wg.Done()
}()
diff --git a/internal/lsp/source/view.go b/internal/lsp/source/view.go
index 0ab43fd..f8f6bdd 100644
--- a/internal/lsp/source/view.go
+++ b/internal/lsp/source/view.go
@@ -40,6 +40,9 @@
// if it is not already part of the snapshot.
GetFile(ctx context.Context, uri span.URI) (VersionedFileHandle, error)
+ // AwaitInitialized waits until the snapshot's view is initialized.
+ AwaitInitialized(ctx context.Context)
+
// IsOpen returns whether the editor currently has a file open.
IsOpen(uri span.URI) bool
@@ -142,9 +145,6 @@
// Shutdown closes this view, and detaches it from its session.
Shutdown(ctx context.Context)
- // AwaitInitialized waits until a view is initialized
- AwaitInitialized(ctx context.Context)
-
// WriteEnv writes the view-specific environment to the io.Writer.
WriteEnv(ctx context.Context, w io.Writer) error