Skip to content

Commit a41b482

Browse files
marcopiracciniaduh95
authored andcommitted
fs: prevent spurious recursive watch events on prefix siblings
Signed-off-by: marcopiraccini <marco.piraccini@gmail.com> PR-URL: #63095Fixes: #58868 Reviewed-By: Paolo Insogna <paolo@cowtech.it> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
1 parent d2ea8b7 commit a41b482

2 files changed

Lines changed: 74 additions & 1 deletion

File tree

‎lib/internal/fs/recursive_watch.js‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ const {
2525
join: pathJoin,
2626
relative: pathRelative,
2727
resolve: pathResolve,
28+
sep: pathSep,
2829
}=require('path');
2930

3031
letinternalSync;
@@ -106,8 +107,10 @@ class FSWatcher extends EventEmitter {
106107
#unwatchFiles(file){
107108
this.#symbolicFiles.delete(file);
108109

110+
constchildPrefix=file+pathSep;
109111
for(constfilenameofthis.#files.keys()){
110-
if(StringPrototypeStartsWith(filename,file)){
112+
if(filename===file||
113+
StringPrototypeStartsWith(filename,childPrefix)){
111114
this.#files.delete(filename);
112115
this.#watchers.get(filename)?.close();
113116
this.#watchers.delete(filename);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
'use strict';
2+
3+
// Regression test for https://github.com/nodejs/node/issues/58868
4+
5+
constcommon=require('../common');
6+
7+
if(common.isIBMi){
8+
common.skip('IBMi does not support `fs.watch()`');
9+
}
10+
11+
if(common.isAIX){
12+
common.skip('folder watch capability is limited in AIX.');
13+
}
14+
15+
// macOS and Windows use the native recursive watcher and are unaffected.
16+
if(common.isMacOS||common.isWindows){
17+
common.skip('regression specific to the JS-based recursive watcher');
18+
}
19+
20+
constassert=require('assert');
21+
constfs=require('fs');
22+
constpath=require('path');
23+
const{ setTimeout }=require('timers/promises');
24+
25+
consttmpdir=require('../common/tmpdir');
26+
tmpdir.refresh();
27+
28+
(async()=>{
29+
constroot=fs.mkdtempSync(path.join(tmpdir.path,'watch-prefix-'));
30+
31+
// Sibling names that share the prefix `foo` with the entries to delete.
32+
fs.mkdirSync(path.join(root,'foo_bar'));
33+
fs.writeFileSync(path.join(root,'foo_bar','file.txt'),'');
34+
fs.mkdirSync(path.join(root,'foo_bar','somedir'));
35+
fs.writeFileSync(path.join(root,'foo_'),'');
36+
37+
// `foo` (empty) exercises the exact-match branch of `#unwatchFiles`.
38+
fs.mkdirSync(path.join(root,'foo'));
39+
40+
// `foo2` has descendants and exercises the `file + sep` prefix branch.
41+
fs.mkdirSync(path.join(root,'foo2'));
42+
fs.writeFileSync(path.join(root,'foo2','inside.txt'),'');
43+
fs.mkdirSync(path.join(root,'foo2','sub'));
44+
45+
constevents=[];
46+
constwatcher=fs.watch(root,{recursive: true},(eventType,filename)=>{
47+
events.push({ eventType, filename });
48+
});
49+
50+
// Allow the watcher to fully attach to existing entries.
51+
awaitsetTimeout(common.platformTimeout(200));
52+
53+
fs.rmdirSync(path.join(root,'foo'));
54+
fs.rmSync(path.join(root,'foo2'),{recursive: true});
55+
56+
// Wait long enough to capture any spurious follow-up events.
57+
awaitsetTimeout(common.platformTimeout(500));
58+
59+
watcher.close();
60+
61+
constisSibling=(f)=>
62+
f==='foo_'||f==='foo_bar'||
63+
f.startsWith('foo_bar'+path.sep);
64+
constspurious=events.filter((e)=>isSibling(e.filename));
65+
assert.deepStrictEqual(
66+
spurious,
67+
[],
68+
`unexpected events for prefix-sibling entries: ${JSON.stringify(spurious)}`,
69+
);
70+
})().then(common.mustCall());

0 commit comments

Comments
 (0)