Skip to content

Commit e72c574

Browse files
committed
internal/lsp: improve error handling while parsing
If the context is canceled (or times out) during parsing, we were previously caching the package with no *ast.Files. Any further LSP queries against that package would fail because the package is already loaded, but none of the files are mapped to the package. Fix by propagating non-parse errors as "fatal" errors in parseFiles. typeCheck will propagate these errors and not cache the package. I also fixed the package cache to not cache errors loading packages. If you get an error like "context canceled" then none of the package's files are mapped to the package. This prevents the package from ever getting unmapped when its files are updated. I also added a retry mechanism where if the current request is not canceled but the package failed to load due to a previous request being canceled, this request can try loading the package again.
1 parent 68211a6 commit e72c574

File tree

3 files changed

+67
-25
lines changed

3 files changed

+67
-25
lines changed

internal/lsp/cache/check.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ func (imp *importer) getPkg(pkgPath string) (*pkg, error) {
4545
}
4646
imp.view.pcache.mu.Lock()
4747
e, ok := imp.view.pcache.packages[pkgPath]
48+
4849
if ok {
4950
// cache hit
5051
imp.view.pcache.mu.Unlock()
@@ -61,9 +62,21 @@ func (imp *importer) getPkg(pkgPath string) (*pkg, error) {
6162
e.pkg, e.err = imp.typeCheck(pkgPath)
6263
close(e.ready)
6364
}
65+
6466
if e.err != nil {
67+
// If the import had been previously canceled, and that error cached, try again.
68+
if e.err == context.Canceled && imp.ctx.Err() == nil {
69+
imp.view.pcache.mu.Lock()
70+
// Clear out canceled cache entry if it is still there.
71+
if imp.view.pcache.packages[pkgPath] == e {
72+
delete(imp.view.pcache.packages, pkgPath)
73+
}
74+
imp.view.pcache.mu.Unlock()
75+
return imp.getPkg(pkgPath)
76+
}
6577
return nil, e.err
6678
}
79+
6780
return e.pkg, nil
6881
}
6982

@@ -103,12 +116,19 @@ func (imp *importer) typeCheck(pkgPath string) (*pkg, error) {
103116
// Ignore function bodies for any dependency packages.
104117
// TODO: Enable this.
105118
ignoreFuncBodies := false
106-
107-
// Don't type-check function bodies if we are not in the top-level package.
108-
files, errs := imp.parseFiles(meta.files, ignoreFuncBodies)
109-
for _, err := range errs {
119+
files, parseErrs, err := imp.parseFiles(meta.files, ignoreFuncBodies)
120+
if err != nil {
121+
return nil, err
122+
}
123+
for _, err := range parseErrs {
110124
appendError(err)
111125
}
126+
127+
// If something unexpected happens, don't cache a package with 0 parsed files.
128+
if len(files) == 0 {
129+
return nil, fmt.Errorf("no parsed files for package %s", pkg.pkgPath)
130+
}
131+
112132
pkg.syntax = files
113133

114134
// Handle circular imports by copying previously seen imports.

internal/lsp/cache/load.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,9 +41,12 @@ func (v *view) loadParseTypecheck(ctx context.Context, f *goFile) ([]packages.Er
4141
}
4242
// Type-check package.
4343
pkg, err := imp.getPkg(f.meta.pkgPath)
44-
if pkg == nil || pkg.IsIllTyped() {
44+
if err != nil {
4545
return nil, err
4646
}
47+
if pkg == nil || pkg.IsIllTyped() {
48+
return nil, fmt.Errorf("loadParseTypecheck: %s is ill typed", f.meta.pkgPath)
49+
}
4750
// If we still have not found the package for the file, something is wrong.
4851
if f.pkg == nil {
4952
return nil, fmt.Errorf("loadParseTypeCheck: no package found for %v", f.filename())

internal/lsp/cache/parse.go

Lines changed: 39 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -28,34 +28,49 @@ func parseFile(fset *token.FileSet, filename string, src []byte) (*ast.File, err
2828
var ioLimit = make(chan bool, 20)
2929

3030
// parseFiles reads and parses the Go source files and returns the ASTs
31-
// of the ones that could be at least partially parsed, along with a
32-
// list of I/O and parse errors encountered.
31+
// of the ones that could be at least partially parsed, along with a list
32+
// parse errors encountered, and a fatal error that prevented parsing.
3333
//
3434
// Because files are scanned in parallel, the token.Pos
3535
// positions of the resulting ast.Files are not ordered.
3636
//
37-
func (imp *importer) parseFiles(filenames []string, ignoreFuncBodies bool) ([]*astFile, []error) {
38-
var wg sync.WaitGroup
39-
n := len(filenames)
40-
parsed := make([]*astFile, n)
41-
errors := make([]error, n)
37+
func (imp *importer) parseFiles(filenames []string, ignoreFuncBodies bool) ([]*astFile, []error, error) {
38+
var (
39+
wg sync.WaitGroup
40+
mu sync.Mutex
41+
n = len(filenames)
42+
parsed = make([]*astFile, n)
43+
errors = make([]error, n)
44+
fatalErr error
45+
)
46+
47+
setFatalErr := func(err error) {
48+
mu.Lock()
49+
fatalErr = err
50+
mu.Unlock()
51+
}
52+
4253
for i, filename := range filenames {
43-
if imp.ctx.Err() != nil {
44-
parsed[i] = nil
45-
errors[i] = imp.ctx.Err()
46-
continue
54+
if err := imp.ctx.Err(); err != nil {
55+
setFatalErr(err)
56+
break
4757
}
4858

4959
// First, check if we have already cached an AST for this file.
5060
f, err := imp.view.findFile(span.FileURI(filename))
51-
if err != nil || f == nil {
52-
parsed[i], errors[i] = nil, err
53-
continue
61+
if err != nil {
62+
setFatalErr(err)
63+
break
5464
}
65+
if f == nil {
66+
setFatalErr(fmt.Errorf("could not find file %s", filename))
67+
break
68+
}
69+
5570
gof, ok := f.(*goFile)
5671
if !ok {
57-
parsed[i], errors[i] = nil, fmt.Errorf("non-Go file in parse call: %v", filename)
58-
continue
72+
setFatalErr(fmt.Errorf("non-Go file in parse call: %s", filename))
73+
break
5974
}
6075

6176
wg.Add(1)
@@ -77,13 +92,13 @@ func (imp *importer) parseFiles(filenames []string, ignoreFuncBodies bool) ([]*a
7792
}
7893

7994
// We don't have a cached AST for this file, so we read its content and parse it.
80-
data, _, err := gof.Handle(imp.ctx).Read(imp.ctx)
95+
src, _, err := gof.Handle(imp.ctx).Read(imp.ctx)
8196
if err != nil {
97+
setFatalErr(err)
8298
return
8399
}
84-
src := data
85100
if src == nil {
86-
parsed[i], errors[i] = nil, fmt.Errorf("no source for %v", filename)
101+
setFatalErr(fmt.Errorf("no source for %v", filename))
87102
return
88103
}
89104

@@ -107,6 +122,10 @@ func (imp *importer) parseFiles(filenames []string, ignoreFuncBodies bool) ([]*a
107122
}
108123
wg.Wait()
109124

125+
if fatalErr != nil {
126+
return nil, nil, fatalErr
127+
}
128+
110129
// Eliminate nils, preserving order.
111130
var o int
112131
for _, f := range parsed {
@@ -126,7 +145,7 @@ func (imp *importer) parseFiles(filenames []string, ignoreFuncBodies bool) ([]*a
126145
}
127146
errors = errors[:o]
128147

129-
return parsed, errors
148+
return parsed, errors, nil
130149
}
131150

132151
// sameFile returns true if x and y have the same basename and denote

0 commit comments

Comments
 (0)