-
Notifications
You must be signed in to change notification settings - Fork 116
Description
TLDR: It seems that file watchers are preventing the exit handler from properly firing on a child process that I have started, specifically when the exit handler is registered inside a SIGINT handler.
Minimal repro code, with problematic line commented out
const child_process = require('child_process');
const fs = require('fs');
const nsfw = require('nsfw');
const proc = child_process.spawn('bash', ['-c', `
trap "echo bash: Got SIGINT, exiting. ; exit 1" SIGINT
sleep infinity
`], { stdio: 'inherit' });
let watcher;
process.on('SIGINT', async () => {
console.log('Got SIGINT.');
if (watcher) {
console.log('Stopping file watcher.');
// await watcher.stop();
watcher = null;
}
console.log('Forwarding SIGINT to child.');
await new Promise((resolve) => {
proc.on('exit', () => {
console.log('Child process exited.');
resolve();
});
proc.kill('SIGINT');
});
process.exit(1);
});
(async () => {
const watchPath = '/tmp/watch_path';
fs.mkdirSync(watchPath, { recursive: true });
(watcher = await nsfw(watchPath, () => {})).start();
console.log('Press Ctrl+C to stop child process');
})();Try running this program and pressing Ctrl+C -- the child exits as expected.
If the commented line is uncommented (await watcher.stop()), the child process exits (I see bash: Got SIGINT, exiting. printed), but the exit listener does not get fired, so the program hangs forever.
Interestingly, if the 'exit' listener is registered before stopping file watchers, everything works fine:
Repro modified to register exit listener before stopping file watchers
const child_process = require('child_process');
const fs = require('fs');
const nsfw = require('nsfw');
const proc = child_process.spawn('bash', ['-c', `
trap "echo bash: Got SIGINT, exiting. ; exit 1" SIGINT
sleep infinity
`], { stdio: 'inherit' });
let watcher;
const onExit = new Promise((resolve) => {
proc.on('exit', () => {
console.log('Child process exited.');
resolve();
});
});
process.on('SIGINT', async () => {
console.log('Got SIGINT.');
if (watcher) {
console.log('Stopping file watcher.');
await watcher.stop();
watcher = null;
}
console.log('Forwarding SIGINT to child.');
proc.kill('SIGINT');
await onExit;
process.exit(1);
});
(async () => {
const watchPath = '/tmp/watch_path';
fs.mkdirSync(watchPath, { recursive: true });
(watcher = await nsfw(watchPath, () => {})).start();
console.log('Press Ctrl+C to stop child process');
})();So it appears that starting file watchers is preventing subsequently registered exit listeners from firing. But maybe it's because I am trying to register the 'exit' listener inside a SIGINT handler? Indeed, it appears to have something to do with the SIGINT handler, because if I stop file watchers and register the exit listener in the main function, it works fine:
Repro modified to stop file watchers and register exit listener in main function
const child_process = require('child_process');
const fs = require('fs');
const nsfw = require('nsfw');
const proc = child_process.spawn('bash', ['-c', `
trap "echo bash: Got SIGINT, exiting. ; exit 1" SIGINT
sleep infinity
`], { stdio: 'inherit' });
let watcher;
let onExit;
process.on('SIGINT', async () => {
console.log('Got SIGINT.');
console.log('Forwarding SIGINT to child.');
proc.kill('SIGINT');
await onExit;
process.exit(1);
});
(async () => {
const watchPath = '/tmp/watch_path';
fs.mkdirSync(watchPath, { recursive: true });
(watcher = await nsfw(watchPath, () => {})).start();
await watcher.stop();
onExit = new Promise((resolve) => {
proc.on('exit', () => {
console.log('Child process exited.');
resolve();
});
});
console.log('Press Ctrl+C to stop child process');
})();I am also pretty sure that this is not a general issue with calling await inside a SIGINT handler, and has something specifically to do with file watchers. If I replace the await watcher.stop() with await new Promise(resolve => setTimeout(resolve, 500)), it works:
Minimal example without use of nsfw, demonstrating that the problem is likely nsfw-specific
const child_process = require('child_process');
const proc = child_process.spawn('bash', ['-c', `
trap "echo bash: Got SIGINT, exiting. ; exit 1" SIGINT
sleep infinity
`], { stdio: 'inherit' });
process.on('SIGINT', async () => {
console.log('Got SIGINT.');
console.log('Waiting a bit...')
await new Promise(resolve => setTimeout(resolve, 500));
console.log('Forwarding SIGINT to child.');
proc.kill('SIGINT');
await new Promise((resolve) => {
proc.on('exit', () => {
console.log('Child process exited.');
resolve();
});
});
process.exit(1);
});
(async () => {
console.log('Press Ctrl+C to stop child process');
})();Linux kernel version: 5.13.0-28-generic
Distro: Ubuntu 20.04