Skip to content

Commit

Permalink
gopls/internal/lsp/cache: actually preload files in parallel
Browse files Browse the repository at this point in the history
snapshot.ReadFile holds the snapshot lock, so calling it concurrently
was pointless. Instead, add a new preloadFiles helper that delegates to
to the underlying FileSource.

For golang/go#57987

Change-Id: If3259ca45260b8a24542d40f7558b5d6bf5018d1
Reviewed-on: https://go-review.googlesource.com/c/tools/+/477975
gopls-CI: kokoro <[email protected]>
Run-TryBot: Robert Findley <[email protected]>
Reviewed-by: Alan Donovan <[email protected]>
TryBot-Result: Gopher Robot <[email protected]>
  • Loading branch information
findleyr committed Mar 21, 2023
1 parent acaae98 commit c3edf5a
Show file tree
Hide file tree
Showing 2 changed files with 38 additions and 12 deletions.
14 changes: 2 additions & 12 deletions gopls/internal/lsp/cache/load.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ import (
"path/filepath"
"sort"
"strings"
"sync"
"sync/atomic"
"time"

Expand Down Expand Up @@ -255,7 +254,7 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadSc
s.dumpWorkspace("load")
s.mu.Unlock()

// Opt: pre-fetch loaded files in parallel.
// Opt: preLoad files in parallel.
//
// Requesting files in batch optimizes the underlying filesystem reads.
// However, this is also currently necessary for correctness: populating all
Expand All @@ -266,16 +265,7 @@ func (s *snapshot) load(ctx context.Context, allowNetwork bool, scopes ...loadSc
// TODO(rfindley, golang/go#57558): determine the set of directories based on
// loaded packages, so that reading files here is not necessary for
// correctness.
var wg sync.WaitGroup
for _, uri := range files {
uri := uri
wg.Add(1)
go func() {
s.ReadFile(ctx, uri) // ignore result
wg.Done()
}()
}
wg.Wait()
s.preloadFiles(ctx, files)

if len(moduleErrs) > 0 {
return &moduleErrorMap{moduleErrs}
Expand Down
36 changes: 36 additions & 0 deletions gopls/internal/lsp/cache/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -1240,6 +1240,42 @@ func (s *snapshot) ReadFile(ctx context.Context, uri span.URI) (source.FileHandl
return lockedSnapshot{s}.ReadFile(ctx, uri)
}

// preloadFiles delegates to the view FileSource to read the requested uris in
// parallel, without holding the snapshot lock.
func (s *snapshot) preloadFiles(ctx context.Context, uris []span.URI) {
files := make([]source.FileHandle, len(uris))
var wg sync.WaitGroup
iolimit := make(chan struct{}, 20) // I/O concurrency limiting semaphore
for i, uri := range uris {
wg.Add(1)
iolimit <- struct{}{}
go func(i int, uri span.URI) {
defer wg.Done()
fh, err := s.view.fs.ReadFile(ctx, uri)
<-iolimit
if err != nil && ctx.Err() == nil {
event.Error(ctx, fmt.Sprintf("reading %s", uri), err)
return
}
files[i] = fh
}(i, uri)
}
wg.Wait()

s.mu.Lock()
defer s.mu.Unlock()

for i, fh := range files {
if fh == nil {
continue // error logged above
}
uri := uris[i]
if _, ok := s.files.Get(uri); !ok {
s.files.Set(uri, fh)
}
}
}

// A lockedSnapshot implements the source.FileSource interface while holding
// the lock for the wrapped snapshot.
type lockedSnapshot struct{ wrapped *snapshot }
Expand Down

0 comments on commit c3edf5a

Please sign in to comment.