Node.js 在 pm2 下 运行 时进程不退出
Node.js process doesn't exit when run under pm2
我有一个 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 秒内保持进程 运行ning 的原因,输出为:
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-liner 中重现了这个:
import got from "got";
await got.post('https://anty-api.com/browser_profiles', {form: {a: 123}}).json();
上面的代码在 运行 形成控制台时按预期抛出,但在被 pm2
.
调用时永远保持 运行ning
更新 2
它也会用一个空的应用程序文件进行重现。
我认为这就是 pm2 的工作方式。您可以预期,当在 pm2 下 运行ning 时,节点进程将永远 运行 继续,(无论您的应用程序是否负责未决的异步事件源)除非您崩溃或做某事明确终止它,例如 process.exit()
.
如您所见,这与您 app.js 中的任何代码都无关。即使是空的 app.js 也会表现出这种行为。这是 pm2 的基本设计方面。它包装你的程序,它是 wrapper 使节点进程保持活动状态。
这是因为 pm2 运行 通过启动节点进程 运行s ProcessContainerFork.js(包装器)。该模块建立并维护与 pm2 管理进程(a.k.a“god daemon”)的连接,并使用 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
之前,您必须确保所有相关的未决承诺或计时器都已解决,因为这将立即终止进程,而无需等待未决事情发生。
我有一个 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 秒内保持进程 运行ning 的原因,输出为:
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-liner 中重现了这个:
import got from "got";
await got.post('https://anty-api.com/browser_profiles', {form: {a: 123}}).json();
上面的代码在 运行 形成控制台时按预期抛出,但在被 pm2
.
更新 2
它也会用一个空的应用程序文件进行重现。
我认为这就是 pm2 的工作方式。您可以预期,当在 pm2 下 运行ning 时,节点进程将永远 运行 继续,(无论您的应用程序是否负责未决的异步事件源)除非您崩溃或做某事明确终止它,例如 process.exit()
.
如您所见,这与您 app.js 中的任何代码都无关。即使是空的 app.js 也会表现出这种行为。这是 pm2 的基本设计方面。它包装你的程序,它是 wrapper 使节点进程保持活动状态。
这是因为 pm2 运行 通过启动节点进程 运行s ProcessContainerFork.js(包装器)。该模块建立并维护与 pm2 管理进程(a.k.a“god daemon”)的连接,并使用 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
之前,您必须确保所有相关的未决承诺或计时器都已解决,因为这将立即终止进程,而无需等待未决事情发生。