Skip to content

Commit c0db45f

Browse files
committed
gopls/internal/server: simplify DiagnoseFiles to avoid a race
Simplify the DiagnoseFiles command handler to just diagnose all snapshots for requested files, rather than implement ad-hoc diagnostic logic. This may be somewhat slower, but this is only used for command line commands and is therefore performance is not critical. Also adjust updateDiagnostics to not overwrite final diagnostics with non-final diagnostics. These two changes together avoid the race encountered in golang/go#64765: DiagnoseFiles does not return until diagnostics are finalized, and these final diagnostics are not overwritten. Fixes golang/go#64765 Change-Id: I54ef7309487a9803a8bbd45ab2a8de4dbf30c460 Reviewed-on: https://go-review.googlesource.com/c/tools/+/556475 Reviewed-by: Alan Donovan <[email protected]> Run-TryBot: Robert Findley <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent 0d1b478 commit c0db45f

File tree

3 files changed

+25
-51
lines changed

3 files changed

+25
-51
lines changed

gopls/internal/lsp/cache/pkg.go

-17
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
package cache
66

77
import (
8-
"context"
98
"fmt"
109
"go/ast"
1110
"go/scanner"
@@ -172,19 +171,3 @@ func (p *Package) GetParseErrors() []scanner.ErrorList {
172171
func (p *Package) GetTypeErrors() []types.Error {
173172
return p.pkg.typeErrors
174173
}
175-
176-
func (p *Package) DiagnosticsForFile(ctx context.Context, uri protocol.DocumentURI) ([]*Diagnostic, error) {
177-
var diags []*Diagnostic
178-
for _, diag := range p.loadDiagnostics {
179-
if diag.URI == uri {
180-
diags = append(diags, diag)
181-
}
182-
}
183-
for _, diag := range p.pkg.diagnostics {
184-
if diag.URI == uri {
185-
diags = append(diags, diag)
186-
}
187-
}
188-
189-
return diags, nil
190-
}

gopls/internal/server/command.go

+16-33
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@ import (
3434
"golang.org/x/tools/gopls/internal/settings"
3535
"golang.org/x/tools/gopls/internal/telemetry"
3636
"golang.org/x/tools/gopls/internal/util/bug"
37-
"golang.org/x/tools/gopls/internal/util/maps"
3837
"golang.org/x/tools/gopls/internal/vulncheck"
3938
"golang.org/x/tools/gopls/internal/vulncheck/scan"
4039
"golang.org/x/tools/internal/diff"
@@ -1301,47 +1300,31 @@ func (c *commandHandler) DiagnoseFiles(ctx context.Context, args command.Diagnos
13011300
ctx, done := event.Start(ctx, "lsp.server.DiagnoseFiles")
13021301
defer done()
13031302

1304-
// TODO(adonovan): opt: parallelize the loop,
1305-
// grouping file URIs by package and making a
1306-
// single call to source.Analyze.
1303+
snapshots := make(map[*cache.Snapshot]bool)
13071304
for _, uri := range args.Files {
13081305
fh, snapshot, release, err := c.s.fileOf(ctx, uri)
13091306
if err != nil {
13101307
return err
13111308
}
1312-
defer release()
1313-
if snapshot.FileKind(fh) != file.Go {
1309+
if snapshots[snapshot] || snapshot.FileKind(fh) != file.Go {
1310+
release()
13141311
continue
13151312
}
1316-
pkg, _, err := source.NarrowestPackageForFile(ctx, snapshot, uri)
1317-
if err != nil {
1318-
return err
1319-
}
1320-
pkgDiags, err := pkg.DiagnosticsForFile(ctx, uri)
1321-
if err != nil {
1322-
return err
1323-
}
1324-
adiags, err := source.Analyze(ctx, snapshot, map[source.PackageID]unit{pkg.Metadata().ID: {}}, nil /* progress tracker */)
1325-
if err != nil {
1326-
return err
1327-
}
1313+
defer release()
1314+
snapshots[snapshot] = true
1315+
}
13281316

1329-
// combine load/parse/type + analysis diagnostics
1330-
var td, ad []*cache.Diagnostic
1331-
combineDiagnostics(pkgDiags, adiags[uri], &td, &ad)
1332-
diags := append(td, ad...)
1333-
byURI := func(d *cache.Diagnostic) protocol.DocumentURI { return d.URI }
1334-
c.s.updateDiagnostics(ctx, c.s.session.Views(), snapshot, maps.Group(diags, byURI), false)
1335-
diagnostics := append(td, ad...)
1336-
1337-
if err := c.s.client.PublishDiagnostics(ctx, &protocol.PublishDiagnosticsParams{
1338-
URI: fh.URI(),
1339-
Version: fh.Version(),
1340-
Diagnostics: toProtocolDiagnostics(diagnostics),
1341-
}); err != nil {
1342-
return err
1343-
}
1317+
var wg sync.WaitGroup
1318+
for snapshot := range snapshots {
1319+
snapshot := snapshot
1320+
wg.Add(1)
1321+
go func() {
1322+
defer wg.Done()
1323+
c.s.diagnoseSnapshot(snapshot, nil, 0)
1324+
}()
13441325
}
1326+
wg.Wait()
1327+
13451328
return nil
13461329
})
13471330
}

gopls/internal/server/diagnostics.go

+9-1
Original file line numberDiff line numberDiff line change
@@ -674,7 +674,15 @@ func (s *server) updateDiagnostics(ctx context.Context, allViews []*cache.View,
674674
// views.
675675
updateAndPublish := func(uri protocol.DocumentURI, f *fileDiagnostics, diags []*cache.Diagnostic) error {
676676
current, ok := f.byView[snapshot.View()]
677-
if !ok || current.snapshot <= snapshot.SequenceID() {
677+
// Update the stored diagnostics if:
678+
// 1. we've never seen diagnostics for this view,
679+
// 2. diagnostics are for an older snapshot, or
680+
// 3. we're overwriting with final diagnostics
681+
//
682+
// In other words, we shouldn't overwrite existing diagnostics for a
683+
// snapshot with non-final diagnostics. This avoids the race described at
684+
// https://github.com/golang/go/issues/64765#issuecomment-1890144575.
685+
if !ok || current.snapshot < snapshot.SequenceID() || (current.snapshot == snapshot.SequenceID() && final) {
678686
fh, err := snapshot.ReadFile(ctx, uri)
679687
if err != nil {
680688
return err

0 commit comments

Comments
 (0)