Skip to content
This repository was archived by the owner on Sep 1, 2026. It is now read-only.

Fix watch coalescing for FSEvents - #4379

Merged
Jake Bailey (jakebailey) merged 9 commits into
mainfrom
jabaile/fix-4378
Jun 26, 2026
Merged

Jake Bailey (jakebailey) merged 9 commits into
mainfrom
jabaile/fix-4378

Conversation

@jakebailey

Copy link
Copy Markdown
Member

Fixes #4378

Coalesce dense directory watch sets into recursive ancestor watches on fast-recursive backends such as FSEvents and Windows. This avoids opening one stream per dependency directory while still surfacing startup failures.

Coalesce dense directory watch sets into recursive ancestor watches on
fast-recursive backends such as FSEvents and Windows. This avoids
opening one stream per dependency directory while still surfacing
startup failures.
@jakebailey

Copy link
Copy Markdown
Member Author

This is doing this in the layer above fswatch, but, going to try and shove it down...

Consolidate dense directory subscriptions inside fswatch for fast
recursive backends. Parent streams now serve later child subscriptions
with per-subscriber filtering, so all fswatch callers benefit without
watch-manager-specific planning.
Add a tsc watch integration baseline covering many dependency files
under a Bun-style node_modules/.bun layout. The lower-level fswatch
tests assert stream coalescing; this keeps the command-line watch shape
covered end-to-end.
# Conflicts:
#	internal/fswatch/watcher.go

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses tsgo --watch startup failures on large directory sets by consolidating many directory watches into fewer recursive watches when the underlying backend supports fast recursion (notably macOS FSEvents and Windows), reducing the likelihood of hitting backend stream/handle limits.

Changes:

  • Add fast-recursive-aware directory watch consolidation in internal/fswatch, including per-subscriber filtering when multiple logical watches share one recursive underlying watch.
  • Extend the in-process LSP watcher backend interface to expose “fast recursive backend” capability and use that when installing some watches.
  • Add regression coverage: a new tscWatch scenario modeled after Bun’s .bun/pkg* layout plus focused fswatch unit tests.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
testdata/baselines/reference/tscWatch/commandLineWatch/watch-handles-many-bun-dependency-files.js Adds expected baseline output for a new watch scenario with many Bun-style dependency directories.
internal/lsp/lspwatcher/lspwatcher.go Exposes backend fast-recursive capability and conditionally requests recursive watching in reconcile logic.
internal/lsp/lspwatcher/lspwatcher_test.go Updates the fake backend to model fast-recursive capability for tests.
internal/fswatch/watcher.go Implements consolidation of dense directory watch sets into recursive ancestor watches for fast-recursive backends and refines callback filtering.
internal/fswatch/watcher_test.go Adds tests validating consolidation behavior and filtering correctness.
internal/execute/tsctests/tscwatch_test.go Adds a new tscWatch scenario that creates many Bun-style dependency files and runs in watch mode.

Comment thread internal/lsp/lspwatcher/lspwatcher.go Resolved
Comment thread internal/fswatch/watcher.go Resolved

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 6 out of 6 changed files in this pull request and generated no new comments.

@jakebailey

Copy link
Copy Markdown
Member Author

I'm not 100% certain about this; the general idea is to coalesce watches of many subdirs into one watch. That staves off the problem a bit at the cost of more watch events that need to be ignored.

Maybe that's fine, but surely we can hit this limit another way?

Maybe we need to have this fall back to kqueue or something on error, if we can even detect when the error is us using too many resources. And we know kqueue is already slow. But, it is what Node uses (not fsevents). Not sure.

@andrewbranch

Copy link
Copy Markdown
Member

The other option is to move this into app logic, like the LSP watcher does unconditionally today and conditionally in https://github.com/andrewbranch/typescript-go/tree/lsp-builtin-watcher-granular.

@jakebailey

Copy link
Copy Markdown
Member Author

That's what I had in the PR originally, but I feel like it's still better to have it pushed down, since theoretically we could make fswatch transparently switch over to kqueue, whereas that would be kind of annoying above.

I need to see if I can make the kqueue backend any faster...

@andrewbranch

Copy link
Copy Markdown
Member

In my experience, kqueue is untenable for the scale of watches we often try to register. I profiled it and it was all syscall time. Nothing to optimize.

@andrewbranch

Copy link
Copy Markdown
Member

I guess, registering them in parallel would probably help, but I still don't think that's a good approach.

@jakebailey

Copy link
Copy Markdown
Member Author

Ultimately, I guess my question is why Strada did not have major issues here, given it could only use kqueue as Node via libuv only did not do fsevents.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

Comment thread internal/fswatch/watcher.go Resolved
Comment thread internal/fswatch/watcher.go Resolved
@jakebailey

Copy link
Copy Markdown
Member Author

I think this PR is fine, but, I think #4447 is the wider fix; might be able to un-coalesce, even.

@jakebailey
Jake Bailey (jakebailey) added this pull request to the merge queue Jun 26, 2026
@jakebailey
Jake Bailey (jakebailey) removed this pull request from the merge queue due to a manual request Jun 26, 2026
@jakebailey
Jake Bailey (jakebailey) added this pull request to the merge queue Jun 26, 2026
Merged via the queue into main with commit b55309c Jun 26, 2026
21 checks passed
@jakebailey
Jake Bailey (jakebailey) deleted the jabaile/fix-4378 branch June 26, 2026 22:10
Jake Bailey (jakebailey) added a commit to jakebailey/TypeScript that referenced this pull request Aug 13, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

tsgo --watch error: "error starting FSEvents stream" on large node_modules since the fswatch rewrite

3 participants