Skip to content

Commit 0bbdf54

Browse files
committed
internal/lsp: modify approach to watching changed files
This change modifies the invalidContent function to take a file change type. This allows us to eliminate the separate invalidateMetadata function. The logic of watching changed files is then further pushed into the caching layer. Updates golang/go#34218 Change-Id: Id31b3931c45ec408b6e7b4a362e00f9091ba4f70 Reviewed-on: https://go-review.googlesource.com/c/tools/+/201221 Run-TryBot: Rebecca Stambler <[email protected]> TryBot-Result: Gobot Gobot <[email protected]> Reviewed-by: Ian Cottrell <[email protected]>
1 parent 8f1b74e commit 0bbdf54

File tree

10 files changed

+122
-123
lines changed

10 files changed

+122
-123
lines changed

internal/lsp/cache/check.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ func (imp *importer) Import(pkgPath string) (*types.Package, error) {
250250
}
251251
cph := imp.snapshot.getPackage(id, source.ParseExported)
252252
if cph == nil {
253-
return nil, errors.Errorf("no package for %s", id)
253+
return nil, errors.Errorf("no cached package for %s", id)
254254
}
255255
pkg, err := cph.check(ctx)
256256
if err != nil {

internal/lsp/cache/file.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ type fileBase struct {
3131
view *view
3232
}
3333

34+
func dir(filename string) string {
35+
return strings.ToLower(filepath.Dir(filename))
36+
}
37+
3438
func basename(filename string) string {
3539
return strings.ToLower(filepath.Base(filename))
3640
}

internal/lsp/cache/load.go

Lines changed: 5 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -105,18 +105,11 @@ func (c *cache) shouldLoad(ctx context.Context, s *snapshot, originalFH, current
105105
return true
106106
}
107107

108-
// Get the original parsed file in order to check package name and imports.
109-
original, _, _, err := c.ParseGoHandle(originalFH, source.ParseHeader).Parse(ctx)
110-
if err != nil {
111-
log.Error(ctx, "no ParseGoHandle for original FileHandle", err, telemetry.URI.Of(originalFH.Identity().URI))
112-
return false
113-
}
114-
115-
// Get the current parsed file in order to check package name and imports.
116-
current, _, _, err := c.ParseGoHandle(currentFH, source.ParseHeader).Parse(ctx)
117-
if err != nil {
118-
log.Error(ctx, "no ParseGoHandle for original FileHandle", err, telemetry.URI.Of(currentFH.Identity().URI))
119-
return false
108+
// Get the original and current parsed files in order to check package name and imports.
109+
original, _, _, originalErr := c.ParseGoHandle(originalFH, source.ParseHeader).Parse(ctx)
110+
current, _, _, currentErr := c.ParseGoHandle(currentFH, source.ParseHeader).Parse(ctx)
111+
if originalErr != nil || currentErr != nil {
112+
return (originalErr == nil) != (currentErr == nil)
120113
}
121114

122115
// Check if the package's metadata has changed. The cases handled are:

internal/lsp/cache/session.go

Lines changed: 20 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,7 @@ func (s *session) removeView(ctx context.Context, view *view) error {
217217
}
218218

219219
// TODO: Propagate the language ID through to the view.
220-
func (s *session) DidOpen(ctx context.Context, uri span.URI, kind source.FileKind, text []byte) {
220+
func (s *session) DidOpen(ctx context.Context, uri span.URI, kind source.FileKind, text []byte) error {
221221
ctx = telemetry.File.With(ctx, uri)
222222

223223
// Files with _ prefixes are ignored.
@@ -227,7 +227,13 @@ func (s *session) DidOpen(ctx context.Context, uri span.URI, kind source.FileKin
227227
view.ignoredURIs[uri] = struct{}{}
228228
view.ignoredURIsMu.Unlock()
229229
}
230-
return
230+
return nil
231+
}
232+
233+
// Make sure that the file gets added to the session's file watch map.
234+
view := s.bestView(uri)
235+
if _, err := view.GetFile(ctx, uri); err != nil {
236+
return err
231237
}
232238

233239
// Mark the file as open.
@@ -236,15 +242,7 @@ func (s *session) DidOpen(ctx context.Context, uri span.URI, kind source.FileKin
236242
// Read the file on disk and compare it to the text provided.
237243
// If it is the same as on disk, we can avoid sending it as an overlay to go/packages.
238244
s.openOverlay(ctx, uri, kind, text)
239-
240-
// Mark the file as just opened so that we know to re-run packages.Load on it.
241-
// We do this because we may not be aware of all of the packages the file belongs to.
242-
// A file may be in multiple views.
243-
for _, view := range s.views {
244-
if strings.HasPrefix(string(uri), string(view.Folder())) {
245-
view.invalidateMetadata(ctx, uri)
246-
}
247-
}
245+
return nil
248246
}
249247

250248
func (s *session) DidSave(uri span.URI) {
@@ -277,7 +275,7 @@ func (s *session) SetOverlay(uri span.URI, kind source.FileKind, data []byte) bo
277275
s.overlayMu.Lock()
278276
defer func() {
279277
s.overlayMu.Unlock()
280-
s.filesWatchMap.Notify(uri)
278+
s.filesWatchMap.Notify(uri, protocol.Changed)
281279
}()
282280

283281
if data == nil {
@@ -299,13 +297,20 @@ func (s *session) SetOverlay(uri span.URI, kind source.FileKind, data []byte) bo
299297
return firstChange
300298
}
301299

300+
func (s *session) clearOverlay(uri span.URI) {
301+
s.overlayMu.Lock()
302+
defer s.overlayMu.Unlock()
303+
304+
delete(s.overlays, uri)
305+
}
306+
302307
// openOverlay adds the file content to the overlay.
303308
// It also checks if the provided content is equivalent to the file's content on disk.
304309
func (s *session) openOverlay(ctx context.Context, uri span.URI, kind source.FileKind, data []byte) {
305310
s.overlayMu.Lock()
306311
defer func() {
307312
s.overlayMu.Unlock()
308-
s.filesWatchMap.Notify(uri)
313+
s.filesWatchMap.Notify(uri, protocol.Created)
309314
}()
310315
s.overlays[uri] = &overlay{
311316
session: s,
@@ -350,16 +355,8 @@ func (s *session) buildOverlay() map[string][]byte {
350355
return overlays
351356
}
352357

353-
func (s *session) DidChangeOutOfBand(ctx context.Context, uri span.URI, changeType protocol.FileChangeType) {
354-
if changeType == protocol.Deleted {
355-
// After a deletion we must invalidate the package's metadata to
356-
// force a go/packages invocation to refresh the package's file list.
357-
views := s.viewsOf(uri)
358-
for _, v := range views {
359-
v.invalidateMetadata(ctx, uri)
360-
}
361-
}
362-
s.filesWatchMap.Notify(uri)
358+
func (s *session) DidChangeOutOfBand(ctx context.Context, uri span.URI, changeType protocol.FileChangeType) bool {
359+
return s.filesWatchMap.Notify(uri, changeType)
363360
}
364361

365362
func (o *overlay) FileSystem() source.FileSystem {

internal/lsp/cache/snapshot.go

Lines changed: 45 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,11 @@ package cache
22

33
import (
44
"context"
5+
"os"
56
"sync"
67

78
"golang.org/x/tools/go/analysis"
9+
"golang.org/x/tools/internal/lsp/protocol"
810
"golang.org/x/tools/internal/lsp/source"
911
"golang.org/x/tools/internal/span"
1012
)
@@ -286,26 +288,60 @@ func (s *snapshot) clone(ctx context.Context, withoutURI *span.URI, withoutTypes
286288

287289
// invalidateContent invalidates the content of a Go file,
288290
// including any position and type information that depends on it.
289-
func (v *view) invalidateContent(ctx context.Context, uri span.URI, kind source.FileKind) {
290-
withoutTypes := make(map[span.URI]struct{})
291-
withoutMetadata := make(map[span.URI]struct{})
291+
func (v *view) invalidateContent(ctx context.Context, f source.File, kind source.FileKind, changeType protocol.FileChangeType) bool {
292+
var (
293+
withoutTypes = make(map[span.URI]struct{})
294+
withoutMetadata = make(map[span.URI]struct{})
295+
ids = make(map[packageID]struct{})
296+
)
292297

293298
// This should be the only time we hold the view's snapshot lock for any period of time.
294299
v.snapshotMu.Lock()
295300
defer v.snapshotMu.Unlock()
296301

297-
ids := v.snapshot.getIDs(uri)
302+
for _, id := range v.snapshot.getIDs(f.URI()) {
303+
ids[id] = struct{}{}
304+
}
305+
306+
switch changeType {
307+
case protocol.Created:
308+
// If this is a file we don't yet know about,
309+
// then we do not yet know what packages it should belong to.
310+
// Make a rough estimate of what metadata to invalidate by finding the package IDs
311+
// of all of the files in the same directory as this one.
312+
// TODO(rstambler): Speed this up by mapping directories to filenames.
313+
if dirStat, err := os.Stat(dir(f.URI().Filename())); err == nil {
314+
for uri := range v.snapshot.files {
315+
if fdirStat, err := os.Stat(dir(uri.Filename())); err == nil {
316+
if os.SameFile(dirStat, fdirStat) {
317+
for _, id := range v.snapshot.ids[uri] {
318+
ids[id] = struct{}{}
319+
}
320+
}
321+
}
322+
}
323+
}
324+
}
325+
326+
if len(ids) == 0 {
327+
return false
328+
}
298329

299330
// Remove the package and all of its reverse dependencies from the cache.
300-
for _, id := range ids {
331+
for id := range ids {
301332
v.snapshot.reverseDependencies(id, withoutTypes, map[packageID]struct{}{})
302333
}
303334

304335
// Get the original FileHandle for the URI, if it exists.
305-
originalFH := v.snapshot.getFile(uri)
336+
originalFH := v.snapshot.getFile(f.URI())
337+
338+
// Make sure to clear out the content if there has been a deletion.
339+
if changeType == protocol.Deleted {
340+
v.session.clearOverlay(f.URI())
341+
}
306342

307343
// Get the current FileHandle for the URI.
308-
currentFH := v.session.GetFile(uri, kind)
344+
currentFH := v.session.GetFile(f.URI(), kind)
309345

310346
// Check if the file's package name or imports have changed,
311347
// and if so, invalidate metadata.
@@ -315,25 +351,9 @@ func (v *view) invalidateContent(ctx context.Context, uri span.URI, kind source.
315351
// TODO: If a package's name has changed,
316352
// we should invalidate the metadata for the new package name (if it exists).
317353
}
354+
uri := f.URI()
318355
v.snapshot = v.snapshot.clone(ctx, &uri, withoutTypes, withoutMetadata)
319-
}
320-
321-
// invalidateMetadata invalidates package metadata for all files in f's
322-
// package. This forces f's package's metadata to be reloaded next
323-
// time the package is checked.
324-
//
325-
// TODO: This function shouldn't be necessary.
326-
// We should be able to handle its use cases more efficiently.
327-
func (v *view) invalidateMetadata(ctx context.Context, uri span.URI) {
328-
v.snapshotMu.Lock()
329-
defer v.snapshotMu.Unlock()
330-
331-
withoutMetadata := make(map[span.URI]struct{})
332-
333-
for _, id := range v.snapshot.getIDs(uri) {
334-
v.snapshot.reverseDependencies(id, withoutMetadata, map[packageID]struct{}{})
335-
}
336-
v.snapshot = v.snapshot.clone(ctx, nil, withoutMetadata, withoutMetadata)
356+
return true
337357
}
338358

339359
// reverseDependencies populates the uris map with file URIs belonging to the

internal/lsp/cache/view.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import (
1818
"golang.org/x/tools/go/packages"
1919
"golang.org/x/tools/internal/imports"
2020
"golang.org/x/tools/internal/lsp/debug"
21+
"golang.org/x/tools/internal/lsp/protocol"
2122
"golang.org/x/tools/internal/lsp/source"
2223
"golang.org/x/tools/internal/span"
2324
"golang.org/x/tools/internal/telemetry/log"
@@ -348,9 +349,9 @@ func (v *view) getFile(ctx context.Context, uri span.URI, kind source.FileKind)
348349
fname: uri.Filename(),
349350
kind: source.Go,
350351
}
351-
v.session.filesWatchMap.Watch(uri, func() {
352+
v.session.filesWatchMap.Watch(uri, func(changeType protocol.FileChangeType) bool {
352353
ctx := xcontext.Detach(ctx)
353-
v.invalidateContent(ctx, uri, kind)
354+
return v.invalidateContent(ctx, f, kind, changeType)
354355
})
355356
v.mapFile(uri, f)
356357
return f, nil

internal/lsp/cache/watcher.go

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ package cache
66

77
import (
88
"sync"
9+
10+
"golang.org/x/tools/internal/lsp/protocol"
911
)
1012

1113
type watcher struct {
1214
id uint64
13-
callback func()
15+
callback func(changeType protocol.FileChangeType) bool
1416
}
1517

1618
type WatchMap struct {
@@ -22,7 +24,7 @@ type WatchMap struct {
2224
func NewWatchMap() *WatchMap {
2325
return &WatchMap{watchers: make(map[interface{}][]watcher)}
2426
}
25-
func (w *WatchMap) Watch(key interface{}, callback func()) func() {
27+
func (w *WatchMap) Watch(key interface{}, callback func(protocol.FileChangeType) bool) func() {
2628
w.mu.Lock()
2729
defer w.mu.Unlock()
2830
id := w.nextID
@@ -47,7 +49,7 @@ func (w *WatchMap) Watch(key interface{}, callback func()) func() {
4749
}
4850
}
4951

50-
func (w *WatchMap) Notify(key interface{}) {
52+
func (w *WatchMap) Notify(key interface{}, changeType protocol.FileChangeType) bool {
5153
// Make a copy of the watcher callbacks so we don't need to hold
5254
// the mutex during the callbacks (to avoid deadlocks).
5355
w.mu.Lock()
@@ -56,7 +58,9 @@ func (w *WatchMap) Notify(key interface{}) {
5658
copy(entriesCopy, entries)
5759
w.mu.Unlock()
5860

61+
var result bool
5962
for _, entry := range entriesCopy {
60-
entry.callback()
63+
result = entry.callback(changeType) || result
6164
}
65+
return result
6266
}

internal/lsp/general.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ func (s *Server) initialized(ctx context.Context, params *protocol.InitializedPa
149149
RegisterOptions: protocol.DidChangeWatchedFilesRegistrationOptions{
150150
Watchers: []protocol.FileSystemWatcher{{
151151
GlobPattern: "**/*.go",
152-
Kind: float64(protocol.WatchChange),
152+
Kind: float64(protocol.WatchChange + protocol.WatchDelete + protocol.WatchCreate),
153153
}},
154154
},
155155
})

internal/lsp/source/view.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ type Session interface {
164164
FileSystem
165165

166166
// DidOpen is invoked each time a file is opened in the editor.
167-
DidOpen(ctx context.Context, uri span.URI, kind FileKind, text []byte)
167+
DidOpen(ctx context.Context, uri span.URI, kind FileKind, text []byte) error
168168

169169
// DidSave is invoked each time an open file is saved in the editor.
170170
DidSave(uri span.URI)
@@ -178,9 +178,9 @@ type Session interface {
178178
// Called to set the effective contents of a file from this session.
179179
SetOverlay(uri span.URI, kind FileKind, data []byte) (wasFirstChange bool)
180180

181-
// DidChangeOutOfBand is called when a file under the root folder
182-
// changes. The file is not necessarily open in the editor.
183-
DidChangeOutOfBand(ctx context.Context, uri span.URI, change protocol.FileChangeType)
181+
// DidChangeOutOfBand is called when a file under the root folder changes.
182+
// If the file was open in the editor, it returns true.
183+
DidChangeOutOfBand(ctx context.Context, uri span.URI, change protocol.FileChangeType) bool
184184

185185
// Options returns a copy of the SessionOptions for this session.
186186
Options() Options

0 commit comments

Comments
 (0)