使用pm2运行Node.js进程时,进程无法退出

3

我有一个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     

为什么Node没有退出?我该如何进一步调试?

PS:抱歉粘贴的内容较大。

更新

我已经成功地将其缩小到了一个有趣的2行代码:

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

以上代码在控制台运行时按预期抛出异常,但在由pm2调用时会一直运行。

更新2:

即使是空的应用程序文件也会重现这个问题。


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

3

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

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

这是因为pm2通过启动运行ProcessContainerFork.js包装器)的节点进程来运行您的程序(在forked模式下,而不是集群模式)。此模块建立并维护与pm2的管理进程(也称为“god daemon”)的连接,并使用require('module')._load(...)加载您的应用程序的主模块。通信渠道始终将计入事件源,从而使实际节点进程保持活动状态。

即使您的程序什么也不做,您的程序的状态也将为“在线”。即使您的程序达到了状态,如果直接启动它,节点将退出,此情况下的状态仍然是“在线”,因为有包装器。

这让pm2的设计者面临一个挑战,即尝试知道您的程序是否不再负责任何事件(在这种情况下,节点通常会退出)。 pm2没有区分您在app.js中编写的代码和由ProcessContainerFork.js建立的基础设施导致节点保持活动状态的原因的功能。人们肯定可以想象,pm2可以使用async_hooks跟踪源自您的应用程序而不是来自ProcessContainerFork.js的事件源(就像howwhy-is-node-running does),然后在到达此状态时适当地关闭。也许pm2选择不这样做是为了避免与async hooks相关的性能损失?也许故意退出但打算重新启动的应用程序太像cron作业了?我猜想您的情况不是pm2的主要用例。我想您可以提出功能请求,并查看pm2作者对此的看法。

我认为这意味着,如果你想要优雅地退出并让 pm2 重启你的程序,你需要调用 process.exit。你不能指望 node 知道没有更多的事件源,因为 pm2 负责其中一些。当然,在调用 process.exit 之前,你必须确保所有相关的未决 promises 或 timers 已经解决,因为这会立即终止进程而不等待未决的事情发生。

我不是pm2的作者。这很可能只是一个疏忽或不足,甚至是一个bug - 我不确定。更熟悉的人可能会有更多的见解,或者知道一些秘密的命令行选项来在适当的时间重新启动它。 - Wyck
感谢您如此详尽的回复。 - eeeeeeeeeeeeeeeeeeeeeeeeeeeeee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接