Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

File watchers prevent exit handler on child process from firing #158

Open
bduffany opened this issue Feb 16, 2022 · 0 comments
Open

File watchers prevent exit handler on child process from firing #158

bduffany opened this issue Feb 16, 2022 · 0 comments

Comments

@bduffany
Copy link

bduffany commented Feb 16, 2022

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant