Repository navigation
FSWatcher.close() incurs a big performance cost in >=10.16 #29949
Copy link
Copy link
Closed as not planned
Labels
fsIssues and PRs related to file-system APIs and the fs module.Issues and PRs related to file-system APIs and the fs module.staleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.Issues and PRs marked stale due to inactivity and scheduled for automatic closure.
Description
- Version: 10.16
- Platform: Mac
- Subsystem: fs
There seems to have been a massive performance decrease in FSWatcher.close() introduced in 10.16.
I use ts-node-dev as a file watcher when developing web applications, which under the hood uses filewatcher.
I believe the following commits/PRs could be contributing? Not smart enough to tell you why though 😅
- fs: improve errors from watch, watchFile and unwatchFile #19345
- fs: improve errors thrown from fs.watch() #19089
Some examples w/ a pretty small express app:
10.15
$ nvm use 10.15
$ tsnd src/index
save some files...
Using ts-node version 8.4.1, typescript version 3.6.4
[INFO] 17:03:16 Restarting: manual restart from user
removing 962 watchers
watcher.removeAll: 5.162ms
Using ts-node version 8.4.1, typescript version 3.6.4
[INFO] 17:03:17 Restarting: manual restart from user
removing 962 watchers
watcher.removeAll: 3.997ms
Using ts-node version 8.4.1, typescript version 3.6.4
[INFO] 17:03:18 Restarting: manual restart from user
removing 962 watchers
watcher.removeAll: 3.915ms
Using ts-node version 8.4.1, typescript version 3.6.4
[INFO] 17:03:18 Restarting: manual restart from user
removing 921 watchers
watcher.removeAll: 2.992ms
10.16
$ nvm use 10.16
$ tsnd src/index
save some files...
Using ts-node version 8.4.1, typescript version 3.6.4
[INFO] 17:05:20 Restarting: manual restart from user
removing 962 watchers
watcher.removeAll: 8631.891ms
Using ts-node version 8.4.1, typescript version 3.6.4
[INFO] 17:05:31 Restarting: manual restart from user
removing 962 watchers
watcher.removeAll: 8679.892ms
Using ts-node version 8.4.1, typescript version 3.6.4
[INFO] 17:05:42 Restarting: manual restart from user
removing 962 watchers
watcher.removeAll: 8679.248ms
Using ts-node version 8.4.1, typescript version 3.6.4
[INFO] 17:05:51 Restarting: manual restart from user
removing 962 watchers
watcher.removeAll: 8764.230ms
Using ts-node version 8.4.1, typescript version 3.6.4
Method
Perf code added to the following: https://github.com/fgnass/filewatcher/blob/master/index.js#L130
FileWatcher.prototype.removeAll = function() {
console.time("watcher.removeAll");
console.log(`removing ${this.list().length} watchers`);
this.list().forEach(this.remove, this);
console.timeEnd("watcher.removeAll");
};
One might be tempted to think the list() method could be slow, however adding code right above it, a single invocation of watcher.close() took longer than all of them in 10.15.
watcher.close ~/project/node_modules/date-fns/getISOWeekYear/index.js: 6.616ms
watcher.close ~/project/node_modules/date-fns/startOfISOWeek/index.js: 6.514ms
watcher.close ~/project/node_modules/date-fns/startOfWeek/index.js: 6.731ms
watcher.close ~/project/node_modules/date-fns/setISOWeekYear/index.js: 6.540ms
watcher.close ~/project/node_modules/date-fns/startOfISOWeekYear/index.js: 6.447ms
watcher.close ~/project/node_modules/date-fns/differenceInCalendarDays/index.js: 6.563ms
watcher.close ~/project/node_modules/date-fns/_lib/getTimezoneOffsetInMilliseconds/index.js: 6.465ms
Reactions are currently unavailable
Metadata
Metadata
Assignees
Labels
fsIssues and PRs related to file-system APIs and the fs module.Issues and PRs related to file-system APIs and the fs module.staleIssues and PRs marked stale due to inactivity and scheduled for automatic closure.Issues and PRs marked stale due to inactivity and scheduled for automatic closure.
Activity
[-]fs.FSWatcher.close() incurs a (large) performance decrease in >=10.16[/-][+]FSWatcher.close() incurs a performance decrease in >=10.16[/+][-]FSWatcher.close() incurs a performance decrease in >=10.16[/-][+]FSWatcher.close() incurs a big performance cost in >=10.16[/+]sebdvd commentedon Sep 9, 2021
Hello, I noticed too that closing a lot of
fs.FSWatcherinstances were slow. I recently migrated some big projects towebpack@5andwebpack-dev-server@4, and I noticed that closing the dev server was slow (cf. discussion here webpack/webpack-dev-server#1479 (comment)). I dug in the source code and found that thewebpackwatch feature useswatchpack@2andfs.watchand takes a lot of time to close all the watchers. In my case it was about closing ~1200fs.FSWatcher. So I wrote a few lines of code to reproduce this issue:On my laptop it's quite slow, here the output:
heyimalex commentedon Aug 5, 2022
Noticing a similar issue on macOS Monterey 12.4, intel, node: v16.16.0. Closing a watcher takes up to 50ms, so closing 1k+ in a tight loop leads to the process being busy for many seconds. Reproducing is trivial: create 5k watchers and then time the closes:
From the output of this it's pretty clear what's happening: the first durations printed are ~35ms and they gradually decrease to sub-ms times, so
FSWatcher.closeperformance scales with the total number of watchers open. Here's close duration plotted against watchers open.Encountered this issue inside watchpack which "intelligently" replaces non-recursive watchers with recursive watchers when the total number of watchers hits some limit. When this replacement happens, watchpack will close the already-opened watchers, and if there are already a lot of files being watched this can take a long time.
Limit the number of concurrently open file watchers on macos
Limit the number of concurrently open file watchers on macos (#73741)
github-actions commentedon Jun 27, 2026
This issue has been marked as stale due to 210 days of inactivity.
It will be automatically closed in 30 days if no further activity occurs. If this is still relevant, please leave a comment or update it to keep it open.
github-actions commentedon Jul 28, 2026
This issue has been automatically closed after 30 days of inactivity following its stale status (no activity for a total of 120 days).
If this is still relevant, feel free to reopen it or leave a comment with additional details so we can continue the discussion.