【问题标题】:Controlling Unix IPC signals in npm scripts在 npm 脚本中控制 Unix IPC 信号
【发布时间】:2020-08-08 14:17:59
【问题描述】:

我正在为节点 API 实现正常关闭。通常,在开发过程中,这个过程是使用常见的start 脚本开始的,但我注意到这会导致一些令人讨厌的行为,我很惊讶在我作为节点开发人员的 3 年左右之前从未真正注意到过这些行为。

要在开发期间开始关闭,我们只需在 bash 终端中按 ctrl+C,这当然会导致将 SIGINT 发送到 npm 进程及其所有子进程。我可以使用process.on 处理程序来捕获这一点,从那里开始正常关闭——关闭新请求,等待现有请求完成,然后终止数据库连接,所有这些都是好事。

但是,如果开发人员再次按 ctrl+C,npm 的行为会有所不同。它似乎正在向 sh 进程发送一个 SIGTERM,它首先用于调用我的 start 脚本。这会导致sh 进程打印出Terminated 并退出,将终端的控制权返回给用户,而无需等待节点进程退出。

这真的很烦人,因为它给人的印象是节点进程已停止,但当然没有。它会一直持续到关机完成,或者被 SIGKILL 或 SIGQUIT 之类的东西强行杀死。如果它碰巧将任何内容打印到控制台,它将直接在开发人员现在可能在该终端中运行的任何其他内容的中间执行。

举个简单的例子,试试这个:

package.json:

{
    "private": true,
    "scripts": {
        "start": "node index.js"
    }
}

index.js:

async function waitForever() {
    while(true) {
        console.log('waiting...');
        await new Promise((resolve) => {
            setTimeout(resolve, 5000);
        });
    }
}

process.on('SIGINT', () => {
    console.log('SIGINT recieved');
});

process.on('SIGTERM', () => {
    console.log('SIGTERM recieved');
})

waitForever();

在终端中运行npm start,然后按一次 ctrl+c。您会看到信号通过节点,但它当然不会退出。现在,再做一次。您会看到信号再次通过,但随后您会立即看到“已终止”,然后是您的 shell 提示符。在您找到节点进程的进程 ID 并使用 kill -9 将其终止之前,您将每五秒看到 waiting... 消息。

我对这个例子做了更多的摆弄,看起来 npm 完全对此负责。如果您将kill -2 直接发送到 npm 进程两次,则会终止 shell 进程,而节点进程永远不会收到 SIGINT。

所以我有两个主要问题:

  1. 这到底是怎么回事?我是否遗漏了有关我的 shell 如何工作的一些信息,或者这是 npm run-script 内置的某种功能?如果是这样,我在哪里可以找到有关此的信息? npm help run-script 对此一无所知。

  2. 我知道start 脚本在这样的项目中很常见,所以似乎其他人应该遇到这个问题。人们通常如何处理它?我用谷歌搜索了一堆,很难找到一个明确的答案。

当然,这没什么大不了的。启动脚本只是为了确保在启动之前运行 TS 编译很方便。我可以让开发人员在构建后直接在他们的 shell 中运行构建的应用程序,或者编写一个脚本来执行构建并在 npm 脚本之外启动。但最好不必这样做。

我真的很困惑,希望能得到一些帮助。谢谢!

【问题讨论】:

    标签: node.js bash shell npm


    【解决方案1】:

    回答您的1 - 查看npm 代码,这是处理SIGINT 的预期行为。第一次出现的信号被传递给子进程,并为后续的SIGINT 附加一次性侦听器,这将立即终止npm 父进程。可以看代码here

    我认为这是因为npm start 仅用作开发阶段的简写,并且在您使用“手刹”来立即终止进程的情况下是有意义的,例如信号处理错误(不幸的是,即使您发现这在所有情况下都不起作用)。

    我对 2 没有答案,但前段时间在关于信号处理的各种npm 问题和npm start 中对此进行了长时间的辩论。 Official NPM statement 主要是 npm start 不能替代适当的流程管理器(例如主管或 systemd),不应该在这样的生产环境中使用。

    编辑:sripberger 对 2 的回答:

    我最终做的事情如下。 shutdown 是一个执行关闭的函数,返回一个承诺。第一个 SIGINT 将开始关闭,但第二个将强制终止进程,无论关闭是否完成。这不会阻止 npm 终止 shell 进程,但它确实会确保节点进程在发生这种情况时随之终止:

    process.once('SIGINT', () => {
        console.log('\nShutting down, please wait...');
    
        // Begin the graceful shutdown.
        shutdown().catch((err) => {
            console.error('Could not shut down gracefully:', err);
        });
    
        // Attach a subsequent handler to force kill.
        process.on('SIGINT', () => {
            console.log('\nProcess killed.');
            process.exit(0);
        });
    });
    

    当然,正如 blami 所说,不建议在生产中使用 npm 脚本来控制服务。这只是对开发环境的一种便利。

    【讨论】:

    • 太棒了,这是一个很好的信息,不仅回答了我的第一个问题。当然,我的计划是在生产中使用适当的流程管理器,我只是觉得尝试避免让开发人员处理孤立的流程是值得的。如果我们可以在这里从某人那里得到关于节点开发人员是否针对这种情况提出了一些约定的编辑,我会将其标记为已接受。
    • 例如,我注意到一个流行的静态 http 服务器是否在后续的 SIGINT 处理程序上:github.com/zeit/serve/blob/…
    猜你喜欢
    • 2022-01-01
    • 2011-01-15
    • 2018-02-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-09-07
    • 2013-10-17
    • 1970-01-01
    相关资源
    最近更新 更多