首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >使用SIGTERM退出子进程(可能是由于超出了maxBuffer);如何确认它是缓冲区问题并修复它?

使用SIGTERM退出子进程(可能是由于超出了maxBuffer);如何确认它是缓冲区问题并修复它?
EN

Stack Overflow用户
提问于 2021-02-25 16:26:02
回答 1查看 513关注 0票数 2

问题

我的子进程在使用SIGTERM大约30分钟后退出,没有其他调试输出。考虑到关于Node.js子进程使用SIGTERM退出的信息,我认为这个过程有可能是由于超出了它的maxBuffer而退出的,因为正常运行时间是不确定的,并且确实是通过增加maxBuffer来改进的。对于默认的205 KB的maxBuffer,它始终运行1-3分钟;在10 MB的情况下,它持续运行30-60分钟。

目标

子进程正在以平均每10分钟大约1MB(每秒1.66KB)的速度生成一个文本流。

文本流中的日志条目是多行的(请参阅组成一个日志条目的行的下面的示例),因此我使用Node逐行解析它们以提取感兴趣的信息(从* << Request >>- End ):

代码语言:javascript
运行
复制
*   << Request  >> 113214123 
-   Begin          req 113214077 rxreq
-   ReqMethod      GET
-   ReqURL         /ping
-   RespStatus     200
-   End   

代码

代码语言:javascript
运行
复制
const { exec } = require('child_process');
const { createInterface } = require('readline');

const cp = exec("tail -F 2021-02-25.log", { maxBuffer: 10000000 });

createInterface(cp.stdout, cp.stdin)
.on('line', line => {
    // ...
    // (Implementation not shown, as it's hundreds of lines long):
    // Add the line to our line-buffer, and if we've reached "-   End   " yet, parse
    // those lines into a corresponding JS object and clear the line-buffer, ready
    // to receive another line.
    // ...
});

cp.on('close', (code, signal) => {
    console.error(`Child process exiting unexpectedly. Code: ${code}; signal: ${signal}.`);
    process.exit(1);
});

问题

本质上,“我如何才能避免获得SIGTERM”--但更具体地说:

  • 如何确认由于子进程超过缓冲区而真正接收到SIGTERM?例如,是否有一种方法可以在子进程运行时检查其缓冲区使用情况?
  • 缓冲区是否由于节点花费太长时间执行行解析函数而超载?有办法监视这件事吗?
  • 我是否遗漏了一个我需要做的额外方面,例如手动刷新一些缓冲区?

我认为在这个问题上抛出额外的缓冲区是解决问题的错误方法;10 MB看起来已经太过了,我需要能够保证无限期的正常运行时间(而不是每次失败时增加一些缓冲区)。

EN

回答 1

Stack Overflow用户

回答已采纳

发布于 2021-02-26 18:50:55

如何诊断子进程因超出其缓冲区而退出

我在Node.js代码库的测试中搜索了对maxBuffer的引用,并找到了一个显示如何诊断因为超出其分配的maxBuffer而退出子进程的测试,我将在这里复制:

代码语言:javascript
运行
复制
// One of the tests from the Node.js codebase:
{
  const cmd =
    `"${process.execPath}" -e "console.log('a'.repeat(1024 * 1024))"`;

  cp.exec(cmd, common.mustCall((err) => {
    assert(err instanceof RangeError);
    assert.strictEqual(err.message, 'stdout maxBuffer length exceeded');
    assert.strictEqual(err.code, 'ERR_CHILD_PROCESS_STDIO_MAXBUFFER');
  }));
}

因此,我在我的应用程序中加入了一个等价的诊断功能:

代码语言:javascript
运行
复制
const { exec } = require('child_process');
const { createInterface } = require('readline');

/**
 * This termination callback is distinct to listening for the "error" event
 * (which does not fire at all, in the case of buffer overflow).
 * @see https://nodejs.org/api/child_process.html#child_process_event_error
 * @see https://nodejs.org/api/child_process.html#child_process_child_process_exec_command_options_callback
 * @param {import("child_process").ExecException | null} error 
 * @param {string} stdout
 * @param {string} stderr 
 * @type {import("child_process").SpawnOptions}
 */
function terminationCallback(error, stdout, stderr){
    if(error === null){
        // Healthy termination. We'll get an exit code and signal from
        // the "close" event handler instead, so will just defer to those
        // logs for debug.
        return;
    }
    console.log(
        `[error] Child process got error with code ${error.code}.` + 
        ` instanceof RangeError: ${error instanceof RangeError}.` +
        ` Error message was: ${error.message}`
    );
    console.log(`stderr (length ${stderr.length}):\n${stderr}`);
    console.log(`stdout (length ${stdout.length}):\n${stdout}`);
}

const cp = exec(
    "tail -F 2021-02-25.log",
    { maxBuffer: 10000000 },
    terminationCallback
);

createInterface(cp.stdout, cp.stdin)
.on('line', line => {
    // ...
    // Implementation not shown
    // ...
});

cp.on('close', (code, signal) => {
    console.error(
        `Child process exiting unexpectedly. ` + 
        `Code: ${code}; signal: ${signal}.`
    );
    process.exit(1);
});

实际上,当我运行我的应用程序几分钟时,我发现这个终止回调是被调用的,并且它满足了在Node.js测试中对于由于超出缓冲区而退出的子进程的所有断言。

我还注意到,在终止回调中返回的stdout正好有1000000个字符长--这与我设置为maxBuffer的字节数完全匹配。正是在这一点上,我才开始理解require("child_process").exec()require("child_process").spawn()之间的区别。

如何使子进程能够安全地从stdout中流出任意数量的数据

主管()产卵()具有重叠的功能,但最终适合于不同的目的,这一点在子过程文件中并没有真正阐明。线索在于他们所接受的建筑论点。

  • exec()接受终止回调,它的选项支持maxBuffer (但不支持斯迪奥)。
  • spawn()不接受终止回调,它的选项支持stdio (但不支持maxBuffer)。

这里的标题是:

  • exec() 适用于具有明确结束的任务(您可以从中获取子进程在其整个工作时间内一直积累到缓冲区中的stdout/stderr )。
  • spawn() 适合于可能无限期地运行的任务,因为您可以配置stdout/stderr/stdin流被管道传输到的位置。options.stdio的默认配置是"pipe",它将它们输送到父进程(您的Node.js应用程序),在我们的示例中,需要建立readline接口并逐行使用stdout。除了操作系统本身施加的缓冲区限制外,没有任何明确的缓冲区限制(这应该是非常慷慨的!)

因此,如果您正在编写一个Node.js应用程序,该应用程序管理一个子进程,任务是无限期运行的:

  • 不间断地监视日志(例如tail -F 2021-02-25.log)并解析它们。
  • 运行始终在线的直播服务(例如,ffmpeg <some complex args here>)

..。你应该使用spawn()

相反,对于具有明确目的和可预测的、合理的缓冲区大小的任务(例如mkdir -vp some/dir/pathrsync --verbose <src> <dest>),您可以继续使用exec()

当然,两者之间可能还有其他区别,但是流处理的这个方面确实是有影响的。

如何使用spawn()重写

只需要更改两行(其中一行仅仅是import语句)!请注意,这里的默认options.stdio"pipe"是适当的,因此我们甚至不需要传入options对象。

代码语言:javascript
运行
复制
const { spawn } = require('child_process');
const { createInterface } = require('readline');

const cp = spawn("tail", ["-F", "2021-02-25.log"]);

createInterface(cp.stdout, cp.stdin)
.on('line', line => {
    // ...
    // (Implementation not shown, as it's hundreds of lines long):
    // Add the line to our line-buffer, and if we've reached "-   End   " yet, parse
    // those lines into a corresponding JS object and clear the line-buffer, ready
    // to receive another line.
    // ...
});

cp.on('close', (code, signal) => {
    console.error(`Child process exiting unexpectedly. Code: ${code}; signal: ${signal}.`);
    process.exit(1);
});
票数 0
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/66372463

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档