Skip to content

Commit

Permalink
internal/lsp: memoize allKnownSubdirs instead of recomputing
Browse files Browse the repository at this point in the history
A lot of the time spent for every file change is recomputing the set of
known subdirectories in the workspace. We can easily memoize these known
subdirectories and avoid recomputing them on every file change. Do that
here and update the set as file creations and deletions come in.

Updates golang/go#45686
Fixes golang/go#45974

Change-Id: Ide07f7c90f0cafc3a3cc7b89ba14ab82d4e3ab28
Reviewed-on: https://go-review.googlesource.com/c/tools/+/317410
Trust: Rebecca Stambler <[email protected]>
Run-TryBot: Rebecca Stambler <[email protected]>
gopls-CI: kokoro <[email protected]>
Reviewed-by: Robert Findley <[email protected]>
TryBot-Result: Go Bot <[email protected]>
  • Loading branch information
stamblerre committed Jun 4, 2021
1 parent 7295a4e commit 1225b6f
Show file tree
Hide file tree
Showing 4 changed files with 97 additions and 28 deletions.
1 change: 1 addition & 0 deletions gopls/internal/regtest/diagnostics/diagnostics_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1569,6 +1569,7 @@ func main() {
env.Await(
env.DiagnosticAtRegexp("main.go", `"mod.com/bob"`),
EmptyDiagnostics("bob/bob.go"),
RegistrationMatching("didChangeWatchedFiles"),
)
})
}
Expand Down
3 changes: 1 addition & 2 deletions internal/lsp/cache/session.go
Original file line number Diff line number Diff line change
Expand Up @@ -553,8 +553,7 @@ func knownDirectories(ctx context.Context, snapshots []*snapshot) map[span.URI]s
for _, dir := range dirs {
result[dir] = struct{}{}
}
subdirs := snapshot.allKnownSubdirs(ctx)
for dir := range subdirs {
for _, dir := range snapshot.getKnownSubdirs(dirs) {
result[dir] = struct{}{}
}
}
Expand Down
120 changes: 94 additions & 26 deletions internal/lsp/cache/snapshot.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,14 @@ type snapshot struct {

workspace *workspace
workspaceDirHandle *memoize.Handle

// knownSubdirs is the set of subdirectories in the workspace, used to
// create glob patterns for file watching.
knownSubdirs map[span.URI]struct{}
// unprocessedSubdirChanges are any changes that might affect the set of
// subdirectories in the workspace. They are not reflected to knownSubdirs
// during the snapshot cloning step as it can slow down cloning.
unprocessedSubdirChanges []*fileChange
}

type packageKey struct {
Expand Down Expand Up @@ -717,7 +725,7 @@ func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]stru
// of the directories in the workspace. We find them by adding the
// directories of every file in the snapshot's workspace directories.
var dirNames []string
for uri := range s.allKnownSubdirs(ctx) {
for _, uri := range s.getKnownSubdirs(dirs) {
dirNames = append(dirNames, uri.Filename())
}
sort.Strings(dirNames)
Expand All @@ -727,40 +735,89 @@ func (s *snapshot) fileWatchingGlobPatterns(ctx context.Context) map[string]stru
return patterns
}

// allKnownSubdirs returns all of the subdirectories within the snapshot's
// workspace directories. None of the workspace directories are included.
func (s *snapshot) allKnownSubdirs(ctx context.Context) map[span.URI]struct{} {
// collectAllKnownSubdirs collects all of the subdirectories within the
// snapshot's workspace directories. None of the workspace directories are
// included.
func (s *snapshot) collectAllKnownSubdirs(ctx context.Context) {
dirs := s.workspace.dirs(ctx, s)

s.mu.Lock()
defer s.mu.Unlock()
seen := make(map[span.URI]struct{})

s.knownSubdirs = map[span.URI]struct{}{}
for uri := range s.files {
dir := filepath.Dir(uri.Filename())
var matched span.URI
for _, wsDir := range dirs {
if source.InDir(wsDir.Filename(), dir) {
matched = wsDir
break
}
}
// Don't watch any directory outside of the workspace directories.
if matched == "" {
s.addKnownSubdirLocked(uri, dirs)
}
}

func (s *snapshot) getKnownSubdirs(wsDirs []span.URI) []span.URI {
s.mu.Lock()
defer s.mu.Unlock()

// First, process any pending changes and update the set of known
// subdirectories.
for _, c := range s.unprocessedSubdirChanges {
if c.isUnchanged {
continue
}
for {
if dir == "" || dir == matched.Filename() {
break
}
uri := span.URIFromPath(dir)
if _, ok := seen[uri]; ok {
break
}
seen[uri] = struct{}{}
dir = filepath.Dir(dir)
if !c.exists {
s.removeKnownSubdirLocked(c.fileHandle.URI())
} else {
s.addKnownSubdirLocked(c.fileHandle.URI(), wsDirs)
}
}
s.unprocessedSubdirChanges = nil

var result []span.URI
for uri := range s.knownSubdirs {
result = append(result, uri)
}
return result
}

func (s *snapshot) addKnownSubdirLocked(uri span.URI, dirs []span.URI) {
dir := filepath.Dir(uri.Filename())
// First check if the directory is already known, because then we can
// return early.
if _, ok := s.knownSubdirs[span.URIFromPath(dir)]; ok {
return
}
var matched span.URI
for _, wsDir := range dirs {
if source.InDir(wsDir.Filename(), dir) {
matched = wsDir
break
}
}
// Don't watch any directory outside of the workspace directories.
if matched == "" {
return
}
for {
if dir == "" || dir == matched.Filename() {
break
}
uri := span.URIFromPath(dir)
if _, ok := s.knownSubdirs[uri]; ok {
break
}
s.knownSubdirs[uri] = struct{}{}
dir = filepath.Dir(dir)
}
}

func (s *snapshot) removeKnownSubdirLocked(uri span.URI) {
dir := filepath.Dir(uri.Filename())
for dir != "" {
uri := span.URIFromPath(dir)
if _, ok := s.knownSubdirs[uri]; !ok {
break
}
if info, _ := os.Stat(dir); info == nil {
delete(s.knownSubdirs, uri)
}
dir = filepath.Dir(dir)
}
return seen
}

// knownFilesInDir returns the files known to the given snapshot that are in
Expand Down Expand Up @@ -1364,6 +1421,7 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
parseModHandles: make(map[span.URI]*parseModHandle, len(s.parseModHandles)),
modTidyHandles: make(map[span.URI]*modTidyHandle, len(s.modTidyHandles)),
modWhyHandles: make(map[span.URI]*modWhyHandle, len(s.modWhyHandles)),
knownSubdirs: make(map[span.URI]struct{}, len(s.knownSubdirs)),
workspace: newWorkspace,
}

Expand Down Expand Up @@ -1409,6 +1467,16 @@ func (s *snapshot) clone(ctx, bgCtx context.Context, changes map[span.URI]*fileC
result.modWhyHandles[k] = v
}

// Add all of the known subdirectories, but don't update them for the
// changed files. We need to rebuild the workspace module to know the
// true set of known subdirectories, but we don't want to do that in clone.
for k, v := range s.knownSubdirs {
result.knownSubdirs[k] = v
}
for _, c := range changes {
result.unprocessedSubdirChanges = append(result.unprocessedSubdirChanges, c)
}

// directIDs keeps track of package IDs that have directly changed.
// It maps id->invalidateMetadata.
directIDs := map[packageID]bool{}
Expand Down
1 change: 1 addition & 0 deletions internal/lsp/cache/view.go
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,7 @@ func (s *snapshot) initialize(ctx context.Context, firstAttempt bool) {
}
s.initializeOnce.Do(func() {
s.loadWorkspace(ctx, firstAttempt)
s.collectAllKnownSubdirs(ctx)
})
}

Expand Down

0 comments on commit 1225b6f

Please sign in to comment.