Commit 76e8b64e5b5 for nodejs
commit 76e8b64e5b5514bd58ee5de5efdd57f0c4d77b8a
Author: Trivikram Kamat <trivikr.dev@gmail.com>
Date: Mon Nov 2 13:19:09 2026 -0800
vfs: forward the watchFile listener through RealFSProvider
fs.watchFile() on a path served by a real-directory mount dispatches
to RealFSProvider.watchFile(), which ignored the supplied listener and
registered an empty function instead. The user callback never fired
after the file changed, and fs.unwatchFile(path, listener) could not
match the anonymous stub, so the stat watcher's poll timer stayed
registered and kept the process alive.
Accept the listener and pass it through to fs.watchFile() so the
callback fires on change and unwatchFile() removes it by identity.
Signed-off-by: Kamat, Trivikram <16024985+trivikr@users.noreply.github.com>
Assisted-by: opencode
PR-URL: https://github.com/nodejs/node/pull/66224
Fixes: https://github.com/nodejs/node/issues/66223
Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
diff --git a/lib/internal/vfs/providers/real.js b/lib/internal/vfs/providers/real.js
index 2f25f582244..a3a5d6329a9 100644
--- a/lib/internal/vfs/providers/real.js
+++ b/lib/internal/vfs/providers/real.js
@@ -632,9 +632,9 @@ class RealFSProvider extends VirtualProvider {
return fs.promises.watch(realPath, options);
}
- watchFile(vfsPath, options) {
+ watchFile(vfsPath, options, listener) {
const realPath = this.#resolvePath(vfsPath);
- return fs.watchFile(realPath, options, () => {});
+ return fs.watchFile(realPath, options, listener);
}
unwatchFile(vfsPath, listener) {
diff --git a/test/parallel/test-vfs-real-provider-watch.js b/test/parallel/test-vfs-real-provider-watch.js
index 03563256202..fc1249837fa 100644
--- a/test/parallel/test-vfs-real-provider-watch.js
+++ b/test/parallel/test-vfs-real-provider-watch.js
@@ -35,10 +35,22 @@ assert.strictEqual(myVfs.provider.supportsWatch, true);
await iter.return();
})().then(common.mustCall());
-// watchFile / unwatchFile
+// watchFile / unwatchFile: the listener must be forwarded to the real fs
+// watcher (not stubbed), fire on change, and be removable by identity.
{
fs.writeFileSync(path.join(root, 'wf.txt'), 'a');
- const listener = () => {};
- myVfs.watchFile('/wf.txt', { persistent: false }, listener);
- myVfs.unwatchFile('/wf.txt', listener);
+ // Unwatch on the first call because writeFileSync truncates before writing,
+ // so a poll landing between the two would report a second change. The
+ // watcher is persistent, so the process only exits once it has been removed.
+ const listener = common.mustCall(() => {
+ myVfs.unwatchFile('/wf.txt', listener);
+ });
+ myVfs.watchFile('/wf.txt', { interval: 10 }, listener);
+ // uv_fs_poll records the first stat asynchronously as the baseline and
+ // only reports subsequent changes, so mutate the file only after that
+ // baseline reflects 'a'; otherwise the change is folded into the baseline
+ // and the listener never fires. Change the size too, because some
+ // platforms (e.g. AIX) only report timestamps to the second, so a
+ // same-size rewrite within that second would look unchanged.
+ setTimeout(() => fs.writeFileSync(path.join(root, 'wf.txt'), 'bb'), 100);
}