Skip to content

Commit 32e1cb7

Browse files
committed
gopls/internal/lsp: clarify control around diagnostics
This CL includes some clarifications while trying to understand the performance of the initial workspace load and analysis. No significant behavior changes. Server.diagnose: - Factor the four copies of the logic for dealing with diagnostics and errors. - Make the ActivePackages blocking step explicit. Previously mod.Diagnostics would do this implicitly, making it look more expensive than it is. Server.addFolders: - eliminate TODO. The logic is not in fact fishy. - use informative names and comments for WaitGroups. - use a channel in place of a non-counting WaitGroup. Also, give pkg a String method. Change-Id: Ia3eff4e784fc04796b636a4635abdfe8ca4e7b5a Reviewed-on: https://go-review.googlesource.com/c/tools/+/445897 Reviewed-by: Robert Findley <[email protected]> gopls-CI: kokoro <[email protected]> Run-TryBot: Alan Donovan <[email protected]> TryBot-Result: Gopher Robot <[email protected]>
1 parent feeb0ba commit 32e1cb7

File tree

4 files changed

+80
-81
lines changed

4 files changed

+80
-81
lines changed

gopls/internal/lsp/cache/pkg.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ type pkg struct {
3636
analyses memoize.Store // maps analyzer.Name to Promise[actionResult]
3737
}
3838

39+
func (p *pkg) String() string { return p.ID() }
40+
3941
// A loadScope defines a package loading scope for use with go/packages.
4042
type loadScope interface {
4143
aScope()

gopls/internal/lsp/diagnostics.go

Lines changed: 50 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -217,6 +217,10 @@ func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot, forceAn
217217
defer done()
218218

219219
// Wait for a free diagnostics slot.
220+
// TODO(adonovan): opt: shouldn't it be the analysis implementation's
221+
// job to de-dup and limit resource consumption? In any case this
222+
// this function spends most its time waiting for awaitLoaded, at
223+
// least initially.
220224
select {
221225
case <-ctx.Done():
222226
return
@@ -226,73 +230,62 @@ func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot, forceAn
226230
<-s.diagnosticsSema
227231
}()
228232

229-
// First, diagnose the go.mod file.
230-
modReports, modErr := mod.Diagnostics(ctx, snapshot)
231-
if ctx.Err() != nil {
232-
log.Trace.Log(ctx, "diagnose cancelled")
233-
return
234-
}
235-
if modErr != nil {
236-
event.Error(ctx, "warning: diagnose go.mod", modErr, tag.Directory.Of(snapshot.View().Folder().Filename()), tag.Snapshot.Of(snapshot.ID()))
237-
}
238-
for id, diags := range modReports {
239-
if id.URI == "" {
240-
event.Error(ctx, "missing URI for module diagnostics", fmt.Errorf("empty URI"), tag.Directory.Of(snapshot.View().Folder().Filename()))
241-
continue
233+
// common code for dispatching diagnostics
234+
store := func(dsource diagnosticSource, operation string, diagsByFileID map[source.VersionedFileIdentity][]*source.Diagnostic, err error) {
235+
if err != nil {
236+
event.Error(ctx, "warning: while "+operation, err,
237+
tag.Directory.Of(snapshot.View().Folder().Filename()),
238+
tag.Snapshot.Of(snapshot.ID()))
239+
}
240+
for id, diags := range diagsByFileID {
241+
if id.URI == "" {
242+
event.Error(ctx, "missing URI while "+operation, fmt.Errorf("empty URI"), tag.Directory.Of(snapshot.View().Folder().Filename()))
243+
continue
244+
}
245+
s.storeDiagnostics(snapshot, id.URI, dsource, diags)
242246
}
243-
s.storeDiagnostics(snapshot, id.URI, modSource, diags)
244247
}
245-
upgradeModReports, upgradeErr := mod.UpgradeDiagnostics(ctx, snapshot)
248+
249+
// Diagnose go.mod upgrades.
250+
upgradeReports, upgradeErr := mod.UpgradeDiagnostics(ctx, snapshot)
246251
if ctx.Err() != nil {
247252
log.Trace.Log(ctx, "diagnose cancelled")
248253
return
249254
}
250-
if upgradeErr != nil {
251-
event.Error(ctx, "warning: diagnose go.mod upgrades", upgradeErr, tag.Directory.Of(snapshot.View().Folder().Filename()), tag.Snapshot.Of(snapshot.ID()))
252-
}
253-
for id, diags := range upgradeModReports {
254-
if id.URI == "" {
255-
event.Error(ctx, "missing URI for module diagnostics", fmt.Errorf("empty URI"), tag.Directory.Of(snapshot.View().Folder().Filename()))
256-
continue
257-
}
258-
s.storeDiagnostics(snapshot, id.URI, modCheckUpgradesSource, diags)
259-
}
260-
vulnerabilityReports, vulnErr := mod.VulnerabilityDiagnostics(ctx, snapshot)
255+
store(modCheckUpgradesSource, "diagnosing go.mod upgrades", upgradeReports, upgradeErr)
256+
257+
// Diagnose vulnerabilities.
258+
vulnReports, vulnErr := mod.VulnerabilityDiagnostics(ctx, snapshot)
261259
if ctx.Err() != nil {
262260
log.Trace.Log(ctx, "diagnose cancelled")
263261
return
264262
}
265-
if vulnErr != nil {
266-
event.Error(ctx, "warning: checking vulnerabilities", vulnErr, tag.Directory.Of(snapshot.View().Folder().Filename()), tag.Snapshot.Of(snapshot.ID()))
267-
}
268-
for id, diags := range vulnerabilityReports {
269-
if id.URI == "" {
270-
event.Error(ctx, "missing URI for module diagnostics", fmt.Errorf("empty URI"), tag.Directory.Of(snapshot.View().Folder().Filename()))
271-
continue
272-
}
273-
s.storeDiagnostics(snapshot, id.URI, modVulncheckSource, diags)
274-
}
263+
store(modVulncheckSource, "diagnosing vulnerabilities", vulnReports, vulnErr)
275264

276-
// Diagnose the go.work file, if it exists.
265+
// Diagnose go.work file.
277266
workReports, workErr := work.Diagnostics(ctx, snapshot)
278267
if ctx.Err() != nil {
279268
log.Trace.Log(ctx, "diagnose cancelled")
280269
return
281270
}
282-
if workErr != nil {
283-
event.Error(ctx, "warning: diagnose go.work", workErr, tag.Directory.Of(snapshot.View().Folder().Filename()), tag.Snapshot.Of(snapshot.ID()))
284-
}
285-
for id, diags := range workReports {
286-
if id.URI == "" {
287-
event.Error(ctx, "missing URI for work file diagnostics", fmt.Errorf("empty URI"), tag.Directory.Of(snapshot.View().Folder().Filename()))
288-
continue
289-
}
290-
s.storeDiagnostics(snapshot, id.URI, workSource, diags)
271+
store(workSource, "diagnosing go.work file", workReports, workErr)
272+
273+
// All subsequent steps depend on the completion of
274+
// type-checking of the all active packages in the workspace.
275+
// This step may take many seconds initially.
276+
// (mod.Diagnostics would implicitly wait for this too,
277+
// but the control is clearer if it is explicit here.)
278+
activePkgs, activeErr := snapshot.ActivePackages(ctx)
279+
280+
// Diagnose go.mod file.
281+
modReports, modErr := mod.Diagnostics(ctx, snapshot)
282+
if ctx.Err() != nil {
283+
log.Trace.Log(ctx, "diagnose cancelled")
284+
return
291285
}
286+
store(modSource, "diagnosing go.mod file", modReports, modErr)
292287

293-
// Diagnose all of the packages in the workspace.
294-
wsPkgs, err := snapshot.ActivePackages(ctx)
295-
if s.shouldIgnoreError(ctx, snapshot, err) {
288+
if s.shouldIgnoreError(ctx, snapshot, activeErr) {
296289
return
297290
}
298291
criticalErr := snapshot.GetCriticalError(ctx)
@@ -303,37 +296,39 @@ func (s *Server) diagnose(ctx context.Context, snapshot source.Snapshot, forceAn
303296
// error progress reports will be closed.
304297
s.showCriticalErrorStatus(ctx, snapshot, criticalErr)
305298

306-
// There may be .tmpl files.
299+
// Diagnose template (.tmpl) files.
307300
for _, f := range snapshot.Templates() {
308301
diags := template.Diagnose(f)
309302
s.storeDiagnostics(snapshot, f.URI(), typeCheckSource, diags)
310303
}
311304

312305
// If there are no workspace packages, there is nothing to diagnose and
313306
// there are no orphaned files.
314-
if len(wsPkgs) == 0 {
307+
if len(activePkgs) == 0 {
315308
return
316309
}
317310

311+
// Run go/analysis diagnosis of packages in parallel.
312+
// TODO(adonovan): opt: it may be more efficient to
313+
// have diagnosePkg take a set of packages.
318314
var (
319315
wg sync.WaitGroup
320316
seen = map[span.URI]struct{}{}
321317
)
322-
for _, pkg := range wsPkgs {
323-
wg.Add(1)
324-
318+
for _, pkg := range activePkgs {
325319
for _, pgf := range pkg.CompiledGoFiles() {
326320
seen[pgf.URI] = struct{}{}
327321
}
328322

323+
wg.Add(1)
329324
go func(pkg source.Package) {
330325
defer wg.Done()
331-
332326
s.diagnosePkg(ctx, snapshot, pkg, forceAnalysis)
333327
}(pkg)
334328
}
335329
wg.Wait()
336330

331+
// Orphaned files.
337332
// Confirm that every opened file belongs to a package (if any exist in
338333
// the workspace). Otherwise, add a diagnostic to the file.
339334
for _, o := range s.session.Overlays() {

gopls/internal/lsp/general.go

Lines changed: 23 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -308,18 +308,18 @@ func (s *Server) addFolders(ctx context.Context, folders []protocol.WorkspaceFol
308308
originalViews := len(s.session.Views())
309309
viewErrors := make(map[span.URI]error)
310310

311-
var wg sync.WaitGroup
311+
var ndiagnose sync.WaitGroup // number of unfinished diagnose calls
312312
if s.session.Options().VerboseWorkDoneProgress {
313313
work := s.progress.Start(ctx, DiagnosticWorkTitle(FromInitialWorkspaceLoad), "Calculating diagnostics for initial workspace load...", nil, nil)
314314
defer func() {
315315
go func() {
316-
wg.Wait()
316+
ndiagnose.Wait()
317317
work.End(ctx, "Done.")
318318
}()
319319
}()
320320
}
321321
// Only one view gets to have a workspace.
322-
var allFoldersWg sync.WaitGroup
322+
var nsnapshots sync.WaitGroup // number of unfinished snapshot initializations
323323
for _, folder := range folders {
324324
uri := span.URIFromURI(folder.URI)
325325
// Ignore non-file URIs.
@@ -338,41 +338,40 @@ func (s *Server) addFolders(ctx context.Context, folders []protocol.WorkspaceFol
338338
}
339339
// Inv: release() must be called once.
340340

341-
var swg sync.WaitGroup
342-
swg.Add(1)
343-
allFoldersWg.Add(1)
344-
// TODO(adonovan): this looks fishy. Is AwaitInitialized
345-
// supposed to be called once per folder?
346-
go func() {
347-
defer swg.Done()
348-
defer allFoldersWg.Done()
349-
snapshot.AwaitInitialized(ctx)
350-
work.End(ctx, "Finished loading packages.")
351-
}()
352-
353341
// Print each view's environment.
354-
buf := &bytes.Buffer{}
355-
if err := snapshot.WriteEnv(ctx, buf); err != nil {
342+
var buf bytes.Buffer
343+
if err := snapshot.WriteEnv(ctx, &buf); err != nil {
356344
viewErrors[uri] = err
357345
release()
358346
continue
359347
}
360348
event.Log(ctx, buf.String())
361349

362-
// Diagnose the newly created view.
363-
wg.Add(1)
350+
// Initialize snapshot asynchronously.
351+
initialized := make(chan struct{})
352+
nsnapshots.Add(1)
353+
go func() {
354+
snapshot.AwaitInitialized(ctx)
355+
work.End(ctx, "Finished loading packages.")
356+
nsnapshots.Done()
357+
close(initialized) // signal
358+
}()
359+
360+
// Diagnose the newly created view asynchronously.
361+
ndiagnose.Add(1)
364362
go func() {
365363
s.diagnoseDetached(snapshot)
366-
swg.Wait()
364+
<-initialized
367365
release()
368-
wg.Done()
366+
ndiagnose.Done()
369367
}()
370368
}
371369

370+
// Wait for snapshots to be initialized so that all files are known.
371+
// (We don't need to wait for diagnosis to finish.)
372+
nsnapshots.Wait()
373+
372374
// Register for file watching notifications, if they are supported.
373-
// Wait for all snapshots to be initialized first, since all files might
374-
// not yet be known to the snapshots.
375-
allFoldersWg.Wait()
376375
if err := s.updateWatchedDirectories(ctx); err != nil {
377376
event.Error(ctx, "failed to register for file watching notifications", err)
378377
}

gopls/internal/lsp/mod/diagnostics.go

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import (
2525
)
2626

2727
// Diagnostics returns diagnostics for the modules in the workspace.
28+
//
29+
// It waits for completion of type-checking of all active packages.
2830
func Diagnostics(ctx context.Context, snapshot source.Snapshot) (map[source.VersionedFileIdentity][]*source.Diagnostic, error) {
2931
ctx, done := event.Start(ctx, "mod.Diagnostics", tag.Snapshot.Of(snapshot.ID()))
3032
defer done()
@@ -73,8 +75,9 @@ func collectDiagnostics(ctx context.Context, snapshot source.Snapshot, diagFn fu
7375
return reports, nil
7476
}
7577

76-
// ModDiagnostics returns diagnostics from diagnosing the packages in the workspace and
77-
// from tidying the go.mod file.
78+
// ModDiagnostics waits for completion of type-checking of all active
79+
// packages, then returns diagnostics from diagnosing the packages in
80+
// the workspace and from tidying the go.mod file.
7881
func ModDiagnostics(ctx context.Context, snapshot source.Snapshot, fh source.FileHandle) (diagnostics []*source.Diagnostic, err error) {
7982
pm, err := snapshot.ParseMod(ctx, fh)
8083
if err != nil {

0 commit comments

Comments
 (0)