Skip to content

Commit fa33ba3

Browse files
authored
test: fix flaky test-watch-mode-kill-signal-*
After the write triggers a restart of the grandchild, the newly spawned second grandchild can post another 'script ready' message before the stdout from the first grandchild is relayed by the watcher and processed by this parent process to kill the watcher. If we write again and trigger another restart, we can end up in an infinite loop and never receive the stdout of the grandchildren in time. Only write once to verify the first grandchild process receives the expected signal. We don't care about the subsequent grandchild processes. PR-URL: #60443 Refs: #60297 Refs: #60391 Reviewed-By: Luigi Pinca <luigipinca@gmail.com> Reviewed-By: Moshe Atlow <moshe@atlow.co.il> Reviewed-By: Darshan Sen <raisinten@gmail.com>
1 parent e1e0830 commit fa33ba3

File tree

3 files changed

+58
-15
lines changed

3 files changed

+58
-15
lines changed
Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,10 @@
1-
process.on('SIGTERM', () => { console.log('__SIGTERM received__'); process.exit(); });
2-
process.on('SIGINT', () => { console.log('__SIGINT received__'); process.exit(); });
3-
process.send('script ready');
1+
process.on('SIGTERM', () => {
2+
console.log(`__SIGTERM received__ ${process.pid}`);
3+
process.exit();
4+
});
5+
process.on('SIGINT', () => {
6+
console.log(`__SIGINT received__ ${process.pid}`);
7+
process.exit();
8+
});
9+
process.send(`script ready ${process.pid}`);
410
setTimeout(() => {}, 100_000);

test/parallel/test-watch-mode-kill-signal-default.mjs

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,20 +26,37 @@ const child = spawn(
2626
);
2727

2828
let stdout = '';
29+
let firstGrandchildPid;
2930
child.stdout.on('data', (data) => {
30-
stdout += `${data}`;
31-
if (/__(SIGINT|SIGTERM) received__/.test(stdout)) {
31+
const dataStr = data.toString();
32+
console.log(`[STDOUT] ${dataStr}`);
33+
stdout += `${dataStr}`;
34+
const match = dataStr.match(/__(SIGINT|SIGTERM) received__ (\d+)/);
35+
if (match && match[2] === firstGrandchildPid) {
36+
console.log(`[PARENT] Sending kill signal to watcher process: ${child.pid}`);
3237
child.kill();
3338
}
3439
});
3540

41+
// After the write triggers a restart of the grandchild, the newly spawned second
42+
// grandchild can post another 'script ready' message before the stdout from the first
43+
// grandchild is relayed by the watcher and processed by this parent process to kill
44+
// the watcher. If we write again and trigger another restart, we can
45+
// end up in an infinite loop and never receive the stdout of the grandchildren in time.
46+
// Only write once to verify the first grandchild process receives the expected signal.
47+
// We don't care about the subsequent grandchild processes.
3648
child.on('message', (msg) => {
37-
if (msg === 'script ready') {
38-
writeFileSync(indexPath, indexContents);
49+
console.log(`[MESSAGE]`, msg);
50+
if (!firstGrandchildPid && typeof msg === 'string') {
51+
const match = msg.match(/script ready (\d+)/);
52+
if (match) {
53+
firstGrandchildPid = match[1]; // This is the first grandchild
54+
writeFileSync(indexPath, indexContents);
55+
}
3956
}
4057
});
4158

4259
await once(child, 'exit');
4360

44-
assert.match(stdout, /__SIGTERM received__/);
45-
assert.doesNotMatch(stdout, /__SIGINT received__/);
61+
assert.match(stdout, new RegExp(`__SIGTERM received__ ${firstGrandchildPid}`));
62+
assert.doesNotMatch(stdout, new RegExp(`__SIGINT received__ ${firstGrandchildPid}`));

test/parallel/test-watch-mode-kill-signal-override.mjs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -27,20 +27,40 @@ const child = spawn(
2727
);
2828

2929
let stdout = '';
30+
let firstGrandchildPid;
3031
child.stdout.on('data', (data) => {
31-
stdout += `${data}`;
32-
if (/__(SIGINT|SIGTERM) received__/.test(stdout)) {
32+
const dataStr = data.toString();
33+
console.log(`[STDOUT] ${dataStr}`);
34+
stdout += `${dataStr}`;
35+
const match = dataStr.match(/__(SIGINT|SIGTERM) received__ (\d+)/);
36+
if (match && match[2] === firstGrandchildPid) {
37+
console.log(`[PARENT] Sending kill signal to watcher process: ${child.pid}`);
3338
child.kill();
3439
}
3540
});
3641

42+
// After the write triggers a restart of the grandchild, the newly spawned second
43+
// grandchild can post another 'script ready' message before the stdout from the first
44+
// grandchild is relayed by the watcher and processed by this parent process to kill
45+
// the watcher. If we write again and trigger another restart, we can
46+
// end up in an infinite loop and never receive the stdout of the grandchildren in time.
47+
// Only write once to verify the first grandchild process receives the expected signal.
48+
// We don't care about the subsequent grandchild processes.
3749
child.on('message', (msg) => {
38-
if (msg === 'script ready') {
39-
writeFileSync(indexPath, indexContents);
50+
console.log(`[MESSAGE]`, msg);
51+
if (!firstGrandchildPid && typeof msg === 'string') {
52+
const match = msg.match(/script ready (\d+)/);
53+
if (match) {
54+
firstGrandchildPid = match[1]; // This is the first grandchild
55+
writeFileSync(indexPath, indexContents);
56+
}
4057
}
4158
});
4259

4360
await once(child, 'exit');
4461

45-
assert.match(stdout, /__SIGINT received__/);
46-
assert.doesNotMatch(stdout, /__SIGTERM received__/);
62+
// The second grandchild, if there is one, could receive SIGTERM if it's killed as a
63+
// consequence of the parent being killed in this process instead of being killed by the
64+
// parent for file changes. Here we only care about the first grandchild.
65+
assert.match(stdout, new RegExp(`__SIGINT received__ ${firstGrandchildPid}`));
66+
assert.doesNotMatch(stdout, new RegExp(`__SIGTERM received__ ${firstGrandchildPid}`));

0 commit comments

Comments
 (0)