【问题标题】:Node.js process doesn't exit when run under pm2Node.js 进程在 pm2 下运行时不退出
【发布时间】:2022-11-11 07:30:33
【问题描述】:

我有一个node.js 脚本可以在控制台中正常运行和退出,但除非我在pm2 中调用process.exit(),否则它不会退出。 PM2 配置为:

        {
            name: "worker",
            script: "./worker.js",
            restart_delay: 60000,
            out_file: "/tmp/worker.log",
            error_file: "/tmp/worker_err.log"
        },

我已经安装了why-is-node-running,以查看是什么让进程在预期退出后 10 秒内保持运行,输出为:



There are 9 handle(s) keeping the process running

# TLSWRAP
node:internal/async_hooks:200

# TLSWRAP
node:internal/async_hooks:200

# ZLIB
node:internal/async_hooks:200                                                 
/Users/r/code/app/node_modules/decompress-response/index.js:43          - const decompressStream = isBrotli ? zlib.createBrotliDecompress() : zlib.createUnzip();
file:///Users/r/code/app/node_modules/got/dist/source/core/index.js:586
file:///Users/r/code/app/node_modules/got/dist/source/core/index.js:768
file:///Users/r/code/app/node_modules/got/dist/source/core/index.js:786

# TLSWRAP
node:internal/async_hooks:200

# ZLIB
node:internal/async_hooks:200                                                 
/Users/r/code/app/node_modules/decompress-response/index.js:43          - const decompressStream = isBrotli ? zlib.createBrotliDecompress() : zlib.createUnzip();
file:///Users/r/code/app/node_modules/got/dist/source/core/index.js:586
file:///Users/r/code/app/node_modules/got/dist/source/core/index.js:768
file:///Users/r/code/app/node_modules/got/dist/source/core/index.js:786

# TLSWRAP
node:internal/async_hooks:200

# ZLIB
node:internal/async_hooks:200                                                 
/Users/r/code/app/node_modules/decompress-response/index.js:43          - const decompressStream = isBrotli ? zlib.createBrotliDecompress() : zlib.createUnzip();
file:///Users/r/code/app/node_modules/got/dist/source/core/index.js:586
file:///Users/r/code/app/node_modules/got/dist/source/core/index.js:768
file:///Users/r/code/app/node_modules/got/dist/source/core/index.js:786

# TLSWRAP
node:internal/async_hooks:200

# Timeout
node:internal/async_hooks:200            
node:internal/async_hooks:468            
node:internal/timers:162                 
node:internal/timers:196                 
file:///Users/r/code/app/worker.js:65
node:internal/process/task_queues:94     

为什么节点不退出?我该如何进一步调试呢?

PS:抱歉贴大了

更新

我设法在一个可笑的小 2 班轮中重现了这一点:

import got from "got";
await got.post('https://anty-api.com/browser_profiles', {form: {a: 123}}).json();

上面的代码在运行窗体控制台时按预期抛出,但在被pm2 调用时会一直运行。

更新 2

它也确实使用空的应用程序文件进行复制。

【问题讨论】:

  • 你在这里有很多细节,但一个重要的步骤是将重现问题所需的代码量减少到可以在此处发布的内容。有趣的是,它在 ZLIB TLSWRAP 和超时的上下文中提到了 got 模块。但是我们没有实际的代码来查看发生了什么。你能把它减少到几行重现问题并发布一些代码吗?
  • 我的第一个想法是:pm2 是否使用与从控制台启动它时相同的权限、参数、环境变量和工作目录来启动您的进程?可能是失败的文件/网络操作,然后忽略解决某些问题。
  • @Wyck,谢谢,我做到了。请检查我的更新
  • 当我尝试这个时,我得到一个 HTTP 401 Unauthorized 响应,got 将其作为异常抛出。您可笑的小复制品无法处理此异常。 pm2 可以区分崩溃(由于未处理的异常)和干净退出之间的区别。这可能是怎么回事?比较:got.post('https://anty-api.com/browser_profiles', {form: {a: 123}}).json().catch(console.error);(或在您等待的方法中尝试/捕获。)
  • 等一下……这会用空的 app.js 文件重现吗?您是否可能只是误解了在 pm2 的上下文中“退出”的含义?你了解ProcessContainerFork.js 的作用吗?节点过程当您的模块返回时,不会在 fork 模式下停止,因为它保持与上帝进程的连接。 (这也是您在手动添加 application.exit() 时观察到重新启动的原因。)如果这使用空的 app.js 重现,那么您应该修改您的问题。

标签: node.js pm2 node.js-got


【解决方案1】:

我认为这只是pm2 的工作方式.您可以预期,当在 pm2 下运行时,节点进程将继续永远运行(无论您的应用程序是否负责挂起的异步事件源),除非您崩溃或执行某些操作来显式终止它,例如 process.exit()

正如您所发现的,这与您的 app.js 中的任何代码无关。即使是空的 app.js 也会表现出这种行为。这是 pm2 的基本设计方面。它包装了你的程序,它是包装即保持节点进程处于活动状态。

这是因为 pm2 通过启动一个运行 ProcessContainerFork.js 的节点进程来运行您的程序(以分叉模式,而不是集群模式)(包装)。该模块建立并维护与 pm2 的管理进程(又名“上帝守护进程”)的连接,并使用 require('module')._load(...) 加载应用程序的主模块。通信通道将始终算作使实际节点进程保持活动状态的事件源。

即使您的程序什么都不做,您的程序的状态也会是“在线”。即使您的程序达到如果直接启动,节点将退出的状态,在这种情况下,由于包装器,状态仍然是“在线”。

这给 pm2 的设计者留下了挑战,即试图知道您的程序是否不再对任何事件负责(在这种情况下节点通常会退出)。 pm2 没有区分由于您在 app.js 中编写的代码而导致节点保持活动的原因与由于 ProcessContainerFork.js 建立的基础设施而导致节点保持活动的原因的功能。可以想象 pm2 可以使用 async_hooks 来跟踪源自您的应用程序而不是来自 ProcessContainerFork.js 的事件源(很像 how why-is-node-running does),然后在达到此状态时正确拆除。也许 pm2 选择不这样做是为了避免与异步挂钩相关的性能损失?也许一个故意退出但打算重新启动的应用程序看起来太像一个 cron 作业?我推测您的不是 pm2 的主要用例。我想你可以提出一个功能请求,看看 pm2 作者对此有何评论。

我认为这意味着如果你想优雅地退出并让 pm2 重新启动你的程序,你需要调用 process.exit 来这样做。您将无法依赖节点知道没有更多的事件源,因为 pm2 负责其中的一些。当然,在调用process.exit 之前,您必须确保所有相关的待处理承诺或计时器都已解决,因为这将立即终止该过程,而无需等待待处理的事情发生。

【讨论】:

  • 我不是 pm2 作者。这很可能只是一个疏忽或缺陷,甚至是一个错误——我不确定。更熟悉的人可能有更多的洞察力或知道一些秘密的命令行选项,以使其在适当的时间重新启动。
  • 感谢如此彻底的回复
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2016-01-03
  • 1970-01-01
  • 2019-03-12
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多