Uh oh!
There was an error while loading. Please reload this page.
fs: watch directories, not files, in recursive fs.watch fallback - #65486
Merged
nodejs-github-bot merged 1 commit intoAug 31, 2026
Merged
Conversation
nodejs-github-bot
commented
Aug 22, 2026
Collaborator
Review requested:
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@## main #65486 +/- ##
==========================================
+ Coverage 90.12% 90.14% +0.02%
==========================================
Files 752 751 -1 Lines 252315 253482 +1167 Branches 47444 47747 +303 ==========================================
+ Hits 227395 228501 +1106 - Misses 16217 16252 +35 - Partials 8703 8729 +26
🚀 New features to boost your workflow:
|
codebytereforce-pushed
the
perf/fs-watch-recursive-linux-dir-watches
branch
from
August 22, 2026 16:57
6a5d660 to
bd967d5CompareMoLow
approved these changes
Aug 23, 2026
nodejs-github-bot
commented
Aug 23, 2026
Collaborator
The JavaScript recursive watcher used on platforms without a native one (notably Linux) armed an fs.watch() handle and a stat() for every file in the tree, and answered every event by stat()ing and re-reading the whole directory it happened in. A 13k-entry tree cost 13.5k inotify watches, ~150 ms and ~50 MB to set up, and appending to one file in a 3900-entry directory cost ~9 ms of CPU per event. A file replaced by rename() (the usual editor save) also stopped being reported, since its watch stayed on the old inode. inotify reports changes to the entries of a watched directory, with their names, so on Linux watch each directory once (symbolic links keep their own watcher, as before), keep the set of known paths, and resolve an event with a single stat() of the named entry: unknown names are added and reported as 'rename', vanished ones are dropped and reported as 'rename', file changes are reported as 'change'. kqueue and event ports only report that a directory changed, so on the other platforms served by this fallback every file keeps its own watcher and a directory event rescans that directory, as before. unref() and ref() now reach the underlying handles. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com>
codebytereforce-pushed
the
perf/fs-watch-recursive-linux-dir-watches
branch
from
August 23, 2026 20:44
bd967d5 to
7774d81CompareMemberAuthor
@MoLow mind taking another look? the non-Linux path changed since your review |
MoLow
approved these changes
Aug 25, 2026
This comment was marked as outdated.
This comment was marked as outdated.
nodejs-github-bot
commented
Aug 30, 2026
Collaborator
Uh oh!
There was an error while loading. Please reload this page.
nodejs-github-bot
commented
Aug 31, 2026
Collaborator
Landed in 2ec0f9a |
aduh95
commented
Aug 31, 2026
Contributor
CI on |
panva
commented
Aug 31, 2026
Member
aduh95 pushed a commit
that referenced
this pull request
Sep 3, 2026
The JavaScript recursive watcher used on platforms without a native one (notably Linux) armed an fs.watch() handle and a stat() for every file in the tree, and answered every event by stat()ing and re-reading the whole directory it happened in. A 13k-entry tree cost 13.5k inotify watches, ~150 ms and ~50 MB to set up, and appending to one file in a 3900-entry directory cost ~9 ms of CPU per event. A file replaced by rename() (the usual editor save) also stopped being reported, since its watch stayed on the old inode. inotify reports changes to the entries of a watched directory, with their names, so on Linux watch each directory once (symbolic links keep their own watcher, as before), keep the set of known paths, and resolve an event with a single stat() of the named entry: unknown names are added and reported as 'rename', vanished ones are dropped and reported as 'rename', file changes are reported as 'change'. kqueue and event ports only report that a directory changed, so on the other platforms served by this fallback every file keeps its own watcher and a directory event rescans that directory, as before. unref() and ref() now reach the underlying handles. Signed-off-by: Shelley Vohr <shelley.vohr@gmail.com> PR-URL: #65486 Reviewed-By: Moshe Atlow <moshe@atlow.co.il>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Makes the JavaScript recursive watcher (used where
fs.watch()has no native recursive mode) arm one watcher per directory instead of one per file on Linux, and answer each event with onestat()instead of re-reading the directory.Linux, this repository's
test/tree (13 500 entries, 896 directories):fs.watch(test, { recursive: true })setupbenchmark/fs/bench-watch-recursive.js(new),test/fixtures, 30 runstest/paralleltest/parallelThe fallback armed an
fs.watch()handle and ran astatSync()for every file in the tree, and handled every event by stat()ing and re-reading the whole directory it happened in. A file replaced byrename()(the usual editor save) also stopped being reported, because its watch stayed on the old inode, andwatcher.ref()/unref()were no-ops on this path.inotify reports changes to a watched directory's entries with the entry name, so on Linux the watcher now keeps one handle per directory (symbolic links keep their own, as before) plus the set of known paths, and resolves an event with a single
stat()of the named entry: new names are reported as'rename'and descended into if they are directories, vanished ones are dropped with their subtree and reported as'rename', changes to known files are'change'. kqueue and event ports only report that the directory itself changed, so on the other platforms this fallback serves (illumos, the BSDs, AIX) files keep a watcher each and a directory event rescans that directory, as today. Two event differences remain, both matching the native watchers: creating a file with content yields'rename'then'change', and a file keeps being reported after it is replaced byrename().ref()/unref()now reach the handles.Tests: all
test-fs-watch-recursive-*,test-fs-watch-ignore-*,test-fs-promises-watch*and watch-mode tests pass in both modes (the per-file mode exercised on Linux by flipping the platform check; recursive ones 3/3 repeated runs); a new Linux-only test covers the per-directory handle count, rename-over reporting, events behind a symbolic link, removal of a watched root directory and root file, andref()/unref().Disclosure: the code, test, benchmark, measurements and this description were written by Claude Code, directed and reviewed by @codebytere.