Node.js - 如何防止中断的 child 进程继续存在?

Node.js - How can I prevent interrupted child processes from surviving?

我发现如果调用脚本被中断,某些 child 进程无法终止。

具体来说,我有一个使用 Ghostscript 执行各种操作的模块:提取页面图像、从切片创建新的 pdf 等。我使用以下命令执行命令和 return 直通流child 的标准输出:

function spawnStream(command, args, storeStdout, cbSuccess) {
  storeStdout = storeStdout || false;

  const child = spawn(command, args);
  const stream = through(data => stream.emit('data', data));

  let stdout = '';
  child.stdout.on('data', data => {
    if (storeStdout === true) stdout += data;
    stream.write(data);
  });

  let stderr = '';
  child.stderr.on('data', data => stderr += data);

  child.on('close', code => {
    stream.emit('end');
    if (code > 0) return stream.emit('error', stderr);
    if (!!cbSuccess) cbSuccess(stdout);
  });

  return stream;
}

这是由以下函数调用的:

function extractPage(pathname, page) {
  const internalRes = 96;
  const downScaleFactor = 1;
  return spawnStream(PATH_TO_GS, [
    '-q',
    '-sstdout=%stderr',
    '-dBATCH',
    '-dNOPAUSE',
    '-sDEVICE=pngalpha',
    `-r${internalRes}`,
    `-dDownScaleFactor=${downScaleFactor}`,
    `-dFirstPage=${page}`,
    `-dLastPage=${page}`,
    '-sOutputFile=%stdout',
    pathname
  ]);
}

哪个是消耗的,比如像这样:

it('given a pdf pathname and page number, returns the image as a stream', () => {
  const document = path.resolve(__dirname, 'samples', 'document.pdf');
  const test = new Promise((resolve, reject) => {
    const imageBlob = extract(document, 1);
    imageBlob.on('data', data => {
      // do nothing in this test
    });

    imageBlob.on('end', () => resolve(true));
    imageBlob.on('error', err => reject(err));
  });

  return Promise.all([expect(test).to.eventually.equal(true)]);
});

当它被中断时,例如,如果测试超时或发生未处理的错误,child 进程似乎没有收到任何信号并继续存在。这有点令人困惑,因为没有哪个单独的操作特别复杂,但该过程似乎会无限期地存在,使用 100% CPU.

☁  ~  ps aux | grep gs | head -n 5
rwick            5735 100.0  4.2  3162908 699484 s000  R    12:54AM   6:28.13 gs -q -sstdout=%stderr -dBATCH -dNOPAUSE -sDEVICE=pngalpha -r96 -dDownScaleFactor=1 -dFirstPage=3 -dLastPage=3 -sOutputFile=%stdout /Users/rwick/projects/xan-desk/test/samples/document.pdf
rwick            5734 100.0  4.2  3171100 706260 s000  R    12:54AM   6:28.24 gs -q -sstdout=%stderr -dBATCH -dNOPAUSE -sDEVICE=pngalpha -r96 -dDownScaleFactor=1 -dFirstPage=2 -dLastPage=2 -sOutputFile=%stdout /Users/rwick/projects/xan-desk/test/samples/document.pdf
rwick            5733 100.0  4.1  3154808 689000 s000  R    12:54AM   6:28.36 gs -q -sstdout=%stderr -dBATCH -dNOPAUSE -sDEVICE=pngalpha -r96 -dDownScaleFactor=1 -dFirstPage=1 -dLastPage=1 -sOutputFile=%stdout /Users/rwick/projects/xan-desk/test/samples/document.pdf
rwick            5732 100.0  4.2  3157360 696556 s000  R    12:54AM   6:28.29 gs -q -sstdout=%stderr -dBATCH -dNOPAUSE -sDEVICE=pdfwrite -sOutputFile=%stdout /Users/rwick/projects/xan-desk/test/samples/document.pdf /Users/rwick/projects/xan-desk/test/samples/page.pdf

我想使用计时器向 child 发送终止信号,但选择任意时间间隔终止进程似乎可以有效地将已知问题换成未知问题并踢掉它在路上。

如果能深入了解我在这里遗漏的内容,我将不胜感激。是否有更好的选择来封装 child 进程,以便 parent 的终止更有可能引发 child 的中断?

监听错误事件

child.on('error', function(err) {
    console.error(err);
    // code
    try {
        // child.kill() or child.disconnect()
    } catch (e) {
        console.error(e);
    }
});