Skip to content

Commit cd39d2b

Browse files
committed
internal/lsp/cache: support loading multiple orphaned files
go/packages returns at most one command-line-arguments package per query. Previously, this led to N^2 loading of orphaned files. CL 480197 fixed this by marking all queried files as unloadable after a load completes. However, as a result gopls would fail to load multiple orphaned files. Fix this properly by calling Load once for each orphaned file. Fixes golang/go#59318 Change-Id: Ibfb3742fcb70ea3976d8b1b5b384fe6b97350cf4 Reviewed-on: https://go-review.googlesource.com/c/tools/+/494401 Reviewed-by: Alan Donovan <[email protected]> gopls-CI: kokoro <[email protected]> Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent abeba28 commit cd39d2b

File tree

3 files changed

+64
-36
lines changed

3 files changed

+64
-36
lines changed

gopls/internal/lsp/cache/load.go

+5-7
Original file line numberDiff line numberDiff line change
@@ -451,14 +451,12 @@ func buildMetadata(updates map[PackageID]*source.Metadata, pkg *packages.Package
451451
pkgPath := PackagePath(pkg.PkgPath)
452452
id := PackageID(pkg.ID)
453453

454-
// TODO(rfindley): this creates at most one command-line-arguments package
455-
// per load, but if we pass multiple file= queries to go/packages, there may
456-
// be multiple command-line-arguments packages.
457-
//
458-
// As reported in golang/go#59318, this can result in accidentally quadratic
459-
// loading behavior.
460454
if source.IsCommandLineArguments(id) {
461-
suffix := ":" + strings.Join(query, ",")
455+
if len(pkg.CompiledGoFiles) != 1 {
456+
bug.Reportf("unexpected files in command-line-arguments package: %v", pkg.CompiledGoFiles)
457+
return
458+
}
459+
suffix := pkg.CompiledGoFiles[0]
462460
id = PackageID(pkg.ID + suffix)
463461
pkgPath = PackagePath(pkg.PkgPath + suffix)
464462
}

gopls/internal/lsp/cache/snapshot.go

+37-29
Original file line numberDiff line numberDiff line change
@@ -1595,40 +1595,52 @@ func (s *snapshot) reloadOrphanedOpenFiles(ctx context.Context) error {
15951595
// available in the snapshot and reload their metadata individually using a
15961596
// file= query if the metadata is unavailable.
15971597
files := s.orphanedOpenFiles()
1598+
if len(files) == 0 {
1599+
return nil
1600+
}
15981601

1599-
// Files without a valid package declaration can't be loaded. Don't try.
1600-
var scopes []loadScope
1602+
var uris []span.URI
16011603
for _, file := range files {
1602-
pgf, err := s.ParseGo(ctx, file, source.ParseHeader)
1603-
if err != nil {
1604-
continue
1605-
}
1606-
if !pgf.File.Package.IsValid() {
1607-
continue
1608-
}
1609-
1610-
scopes = append(scopes, fileLoadScope(file.URI()))
1604+
uris = append(uris, file.URI())
16111605
}
16121606

1613-
if len(scopes) == 0 {
1614-
return nil
1615-
}
1607+
event.Log(ctx, "reloadOrphanedFiles reloading", tag.Files.Of(uris))
16161608

1617-
// The regtests match this exact log message, keep them in sync.
1618-
event.Log(ctx, "reloadOrphanedFiles reloading", tag.Query.Of(scopes))
1619-
err := s.load(ctx, false, scopes...)
1609+
var g errgroup.Group
1610+
1611+
cpulimit := runtime.GOMAXPROCS(0)
1612+
g.SetLimit(cpulimit)
1613+
1614+
// Load files one-at-a-time. go/packages can return at most one
1615+
// command-line-arguments package per query.
1616+
for _, file := range files {
1617+
file := file
1618+
g.Go(func() error {
1619+
pgf, err := s.ParseGo(ctx, file, source.ParseHeader)
1620+
if err != nil || !pgf.File.Package.IsValid() {
1621+
return nil // need a valid header
1622+
}
1623+
return s.load(ctx, false, fileLoadScope(file.URI()))
1624+
})
1625+
}
16201626

16211627
// If we failed to load some files, i.e. they have no metadata,
16221628
// mark the failures so we don't bother retrying until the file's
16231629
// content changes.
16241630
//
16251631
// TODO(rfindley): is it possible that the load stopped early for an
16261632
// unrelated errors? If so, add a fallback?
1627-
//
1628-
// Check for context cancellation so that we don't incorrectly mark files
1629-
// as unloadable, but don't return before setting all workspace packages.
1630-
if ctx.Err() != nil {
1631-
return ctx.Err()
1633+
1634+
if err := g.Wait(); err != nil {
1635+
// Check for context cancellation so that we don't incorrectly mark files
1636+
// as unloadable, but don't return before setting all workspace packages.
1637+
if ctx.Err() != nil {
1638+
return ctx.Err()
1639+
}
1640+
1641+
if !errors.Is(err, errNoPackages) {
1642+
event.Error(ctx, "reloadOrphanedFiles: failed to load", err, tag.Files.Of(uris))
1643+
}
16321644
}
16331645

16341646
// If the context was not canceled, we assume that the result of loading
@@ -1637,19 +1649,15 @@ func (s *snapshot) reloadOrphanedOpenFiles(ctx context.Context) error {
16371649
// prevents us from falling into recursive reloading where we only make a bit
16381650
// of progress each time.
16391651
s.mu.Lock()
1640-
for _, scope := range scopes {
1652+
defer s.mu.Unlock()
1653+
for _, file := range files {
16411654
// TODO(rfindley): instead of locking here, we should have load return the
16421655
// metadata graph that resulted from loading.
1643-
uri := span.URI(scope.(fileLoadScope))
1656+
uri := file.URI()
16441657
if s.noValidMetadataForURILocked(uri) {
16451658
s.unloadableFiles[uri] = struct{}{}
16461659
}
16471660
}
1648-
s.mu.Unlock()
1649-
1650-
if err != nil && !errors.Is(err, errNoPackages) {
1651-
event.Error(ctx, "reloadOrphanedFiles: failed to load", err, tag.Query.Of(scopes))
1652-
}
16531661

16541662
return nil
16551663
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
This test verifies that we can load multiple orphaned files as
2+
command-line-arguments packages.
3+
4+
Previously, we would load only one because go/packages returns at most one
5+
command-line-arguments package per query.
6+
7+
-- a/main.go --
8+
package main
9+
10+
func main() {
11+
var a int //@diag(re"var (a)", re"not used")
12+
}
13+
-- b/main.go --
14+
package main
15+
16+
func main() {
17+
var b int //@diag(re"var (b)", re"not used")
18+
}
19+
-- c/go.mod --
20+
module c.com // The existence of this module avoids a workspace error.
21+
22+
go 1.18

0 commit comments

Comments
 (0)