fs: watch directories, not files, in recursive fs.watch fallback
The Issue & Context
The recursive fallback for fs.watch() on Linux previously created one watcher per file and performed expensive stat() and directory re-reads on every event, leading to high resource usage and slow performance when watching large directory trees.
Root Cause Analysis
The fallback approach did not leverage Linux's inotify behavior of reporting directory entry changes with the entry name, causing redundant watchers and excessive stat calls. Additionally, file replacements via rename() were not properly tracked, causing missed events.
Solution Strategy & Architectural Pattern
The implementation was changed to arm one fs.watch() handle per directory (with symbolic links handled separately) and maintain a set of known paths. Events are resolved with a single stat() call on the changed entry, interpreting new entries as 'rename' and recursing into directories, while dropped entries are removed and reported as 'rename'. This reduced watchers, memory, and CPU usage significantly on large trees.
Key Takeaway for Contributors
Leveraging platform-specific filesystem event semantics can drastically improve watcher efficiency and correctness. Per-directory watching on Linux aligns with inotify's design, reducing resource consumption and improving event accuracy, especially for large recursive watches.