Skip to content

Conversation

andrewbranch
Copy link
Member

@andrewbranch andrewbranch commented Sep 19, 2025

In a large internal monorepo, this cut the number of individual globs we ask the LSP client to watch from 7667 (duplicated and narrower) globs to 7 (deduped and broader) globs, and cut the time it took to compute and register those globs by 80%. This is the basis for two follow-up fixes/improvements:

  1. Change our registered globs to be extensionless in order to work around API: file watching doesn't report file deletions caused by directory deletion vscode#109754
  2. Clear the file cache manually when a large number of files change in a short period of time, as some events are dropped by VS Code (LSP: changes to files not open in editor are sometimes not picked up #1646)

I want to get reviews on this first, as those fixes will build on this simplification.

@andrewbranch andrewbranch changed the title Reduce number of LSP file watcher registrations Simplify and consolidate LSP watcher registrations Sep 30, 2025
@andrewbranch andrewbranch marked this pull request as ready for review September 30, 2025 19:45
@Copilot Copilot AI review requested due to automatic review settings September 30, 2025 19:45
// !!! what about declaration files in node_modules? wouldn't it be better to
// check project inclusion if the project is already loaded?
if !config.MatchesFileName(fileName) {
if _, ok := config.FileNamesByPath()[path]; !ok {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This was a drive-by fix, but MatchesFileName is a more expensive function that speculates about files that may not exist; the usage here was for a file that already exists and is either properly included by the config or not.

assert.Check(t, lsAfter.GetProgram() != programBefore)
})

t.Run("change program file not in tsconfig root files", func(t *testing.T) {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test case exercises logic that was missing from the pre-snapshot LSP port.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The removals in this file represent two strategy changes:

  • Since we rely on the LSP client to handle the watchers at the system level, remove our logic for avoiding installing watchers too close to the file system root. VS Code installs a watcher at the workspace level no matter that directory’s location, so we have nothing to gain by not using it. Any decision to refuse installing a watcher at a given location belongs to the client; they can simply return an error when we request to watch that location.
  • Prefer watching the workspace directory over anything else. If a file is in the workspace, don't register any additional watcher.

Copy link
Contributor

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.

}

// Porting reference: ProjectService.isMatchedByConfig
func (p *ParsedCommandLine) MatchesFileName(fileName string) bool {
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

MatchesFileName was no longer being used except for the incorrect usage I pointed out above. When I initially implemented it for file watching, it was a bad performance bottleneck. PossiblyMatchesFileName is less precise but faster. It may be worth revisiting this if we invest into perf improvements of our include/exclude globbing (#1483)

}

cwd := s.cwd
if s.initializeParams.Capabilities != nil &&
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Boy do I want to add protobuf-style safe accessors for capabilities

@jakebailey
Copy link
Member

https://gist.github.com/jakebailey/84d5310acdb3397056009e0bbf998070 The big list of paths is me logging all of the files from createResolutionLookupGlobMapper (sorted).

@andrewbranch
Copy link
Member Author

@jakebailey could you try the latest? I walked back some of the deletions while keeping it still much simpler than before.

@jakebailey
Copy link
Member

Seems better:

[13:46:59.337] Added new watch: root files for /home/jabaile/work/TypeScript/src/compiler/tsconfig.json watcher 9.0
[13:46:59.337] 	/home/jabaile/work/TypeScript/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}
[13:46:59.337] 
[13:46:59.344] Added new watch: non-root program files for /home/jabaile/work/TypeScript/src/compiler/tsconfig.json watcher 10.1
[13:46:59.344] 	/home/jabaile/work/TypeScript-go/built/local/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}
[13:46:59.344] 
[13:46:59.352] Added new watch: failed lookups for /home/jabaile/work/TypeScript/src/compiler/tsconfig.json watcher 12.0
[13:46:59.352] 	/home/jabaile/work/TypeScript/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}
[13:46:59.352] 
[13:46:59.353] Added new watch: failed lookups for /home/jabaile/work/TypeScript/src/compiler/tsconfig.json watcher 12.1
[13:46:59.353] 	/node_modules/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}
[13:46:59.353] 
[13:46:59.354] Added new watch: failed lookups for /home/jabaile/work/TypeScript/src/compiler/tsconfig.json watcher 12.2
[13:46:59.355] 	/home/jabaile/work/node_modules/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}
[13:46:59.355] 
[13:46:59.356] Added new watch: failed lookups for /home/jabaile/work/TypeScript/src/compiler/tsconfig.json watcher 12.3
[13:46:59.356] 	/home/node_modules/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}
[13:46:59.356] 
[13:46:59.357] Added new watch: failed lookups for /home/jabaile/work/TypeScript/src/compiler/tsconfig.json watcher 12.4
[13:46:59.357] 	/home/jabaile/node_modules/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}
[13:46:59.357] 
[13:46:59.357] Updated watches in 62.427381ms

Those are all definitely safe paths to watch.

@jakebailey
Copy link
Member

Though, the workspace ones seem potentially redundant? (Depending on how the editor does things)

@andrewbranch
Copy link
Member Author

It's not reflected in the logs here, but they appear duplicated because the failed lookups one is a create-only watcher, while the root files for the tsconfig one is a create/change/delete watcher. In theory, one of those could be independently removed if you start opening and closing other projects. We could make a special rule that says “any watcher on the workspace directory is always create/change/delete” but I’m not sure how worthwhile that is.

@jakebailey
Copy link
Member

This is a weird one; I opened DT tools to packages/mergebot/src/pr-info.ts, then opened packages/publisher/src/lib/secrets.ts and got this new watch:

[14:01:36.107] Added new watch: failed lookups for /home/jabaile/work/DefinitelyTyped-tools/packages/publisher/tsconfig.json watcher 37.5
[14:01:36.107] 	/home/jabail/typescript/**/*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,json}
[14:01:36.107] 
[14:01:36.108] Updated watches in 13.083169ms

But, that path doesn't exist and isn't mentioned anywhere else in the logs. This is a failed lookup so maybe there's a bad import somewhere that's not resolving, but I can't seem to find it.

@jakebailey
Copy link
Member

Just realized, /home/jabail/typescript/ doesn't make sense because my username is jabaile. So definitely some string weirdness.

@andrewbranch
Copy link
Member Author

andrewbranch commented Oct 6, 2025

It’s happening to me too, but it’s not in a bug in the watcher code; that’s an actual module resolution lookup 😮

image

@andrewbranch
Copy link
Member Author

It looks like the same bug exists in Strada. I prevented the bogus path lookup for now but I’m not sure if a different strategy should be implemented to find the proper peer dep lookup location, or if it doesn’t matter for monorepo packages like this. (@sheetalkamat you might want to look at the last commit)

Copy link
Member

@jakebailey jakebailey left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't find any weird issues when I try this out and read the logs. Worth a shot!

@jakebailey
Copy link
Member

jakebailey commented Oct 6, 2025

I'll note that in the TS repo, if I delete node_modules, nothing changes; there's no event for this. But, that happens on main, too. Which I believe is that known VS Code issue.

@andrewbranch
Copy link
Member Author

Yeah, that’s microsoft/vscode#109754, and will be handled in a follow-up PR to remove the extensions from our glob patterns.

@andrewbranch andrewbranch enabled auto-merge October 6, 2025 23:00
@andrewbranch
Copy link
Member Author

I merged main because license/cla was MIA, but I have not super high hopes that a new commit is going to unstick it...

@jakebailey
Copy link
Member

A close reopen usually fixes it for me.

auto-merge was automatically disabled October 6, 2025 23:09

Pull request was closed

@andrewbranch andrewbranch reopened this Oct 6, 2025
@andrewbranch andrewbranch enabled auto-merge October 6, 2025 23:09
auto-merge was automatically disabled October 7, 2025 17:03

Pull request was closed

@andrewbranch andrewbranch reopened this Oct 7, 2025
@andrewbranch andrewbranch enabled auto-merge October 7, 2025 17:03
@andrewbranch andrewbranch added this pull request to the merge queue Oct 7, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants