Node.js -如何防止被中断的子进程存活

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

本文关键字:子进程 中断 js 何防止 Node      更新时间:2023-09-26

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

具体来说,我有一个使用Ghostscript执行各种操作的模块:提取页面图像,从切片创建新的pdf等。我使用以下命令来执行该命令,并返回子标准输出的through流:

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)]);
});

当这被中断时,例如,如果测试超时或发生未处理的错误,子进程似乎没有接收到任何信号并存活下来。这有点令人困惑,因为没有一个单独的操作特别复杂,但进程似乎可以无限期地生存,使用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.on('error', function(err) {
    console.error(err);
    // code
    try {
        // child.kill() or child.disconnect()
    } catch (e) {
        console.error(e);
    }
});