Skip to content

Commit b082901

Browse files
committed
test(watcher): cover native watcher watch/close races
Drives `binding.NativeWatcher` directly: several overlapping watch tasks on one watcher followed by an immediate close, repeated. Reproduces the use-after-free (SIGSEGV) on every run without the fix.
1 parent 89f8122 commit b082901

3 files changed

Lines changed: 63 additions & 0 deletions

File tree

pnpm-lock.yaml

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const fs = require("node:fs");
2+
const os = require("node:os");
3+
const path = require("node:path");
4+
const binding = require("@rspack/binding");
5+
6+
const ITERATIONS = 30;
7+
const WATCHES_PER_ITERATION = 5;
8+
const FILE_COUNT = 400;
9+
10+
let dir;
11+
let files;
12+
let dirs;
13+
14+
const startWatch = watcher => {
15+
watcher.watch(
16+
[files, []],
17+
[dirs, []],
18+
[[], []],
19+
BigInt(Date.now()),
20+
() => {},
21+
() => {}
22+
);
23+
};
24+
25+
describe("NativeWatcher lifecycle", () => {
26+
beforeAll(() => {
27+
dir = fs.mkdtempSync(path.join(os.tmpdir(), "rspack-native-watcher-"));
28+
files = [];
29+
dirs = [dir];
30+
for (let i = 0; i < FILE_COUNT; i++) {
31+
const sub = path.join(dir, `sub${i % 20}`);
32+
fs.mkdirSync(sub, { recursive: true });
33+
const file = path.join(sub, `f${i}.js`);
34+
fs.writeFileSync(file, `module.exports = ${i};`);
35+
files.push(file);
36+
if (!dirs.includes(sub)) {
37+
dirs.push(sub);
38+
}
39+
}
40+
});
41+
42+
afterAll(() => {
43+
fs.rmSync(dir, { recursive: true, force: true });
44+
});
45+
46+
it("does not crash when watch and close race on the same watcher", async () => {
47+
for (let i = 0; i < ITERATIONS; i++) {
48+
const watcher = new binding.NativeWatcher({ aggregateTimeout: 1 });
49+
50+
for (let j = 0; j < WATCHES_PER_ITERATION; j++) {
51+
startWatch(watcher);
52+
}
53+
54+
await watcher.close();
55+
56+
expect(() => startWatch(watcher)).toThrow(/has been closed/);
57+
}
58+
});
59+
});

tests/rspack-test/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"@module-federation/runtime-tools": "2.7.0",
1919
"@prefresh/core": "1.5.10",
2020
"@prefresh/utils": "1.2.1",
21+
"@rspack/binding": "workspace:*",
2122
"@rspack/binding-testing": "workspace:*",
2223
"@rspack/cli": "workspace:*",
2324
"@rspack/core": "workspace:*",

0 commit comments

Comments
 (0)