ExpressJS调用app.listen后Node进程无法正常退出如何解决
问题环境
- NodeJS v16.15.1
- Windows 10 操作系统
问题表现
- 代码中调用Express的
app.listen()启动服务后,nodemon、VSCode均无法正常终止对应的Node进程 - 代码变更触发nodemon自动重启时,控制台仅输出
[nodemon] restarting due to changes...后就无后续响应,必须手动打开任务管理器结束对应进程才能继续操作 - 手动在nodemon控制台输入
rs指令触发重启同样卡死无响应,添加nodemon --signal SIGTERM启动参数也无改善,进程始终无法接收SIGTERM信号 - 移除
app.listen()调用后,nodemon和VSCode的进程重启功能完全恢复正常 - 异常触发时无任何报错信息,进程仅卡住不退出
问题最小复现代码:
'use strict'; const express = require('express'); const app = express(); const PORT = 8040; // 问题触发代码行 app.listen(PORT, () => { console.log(`Started on http://localhost:${PORT} (PID: ${process.pid})`); });
手动触发重启卡死截图:
使用why-is-node-running包检测维持进程运行的句柄,结果如下:
There are 5 handle(s) keeping the process running # TCPSERVERWRAP C:\services\overview\node_modules\express\lib\application.js:635 - return server.listen.apply(server, arguments); C:\services\overview\src\tmp2.js:6 - app.listen(PORT, () => { # TTYWRAP C:\services\overview\src\tmp2.js:7 - console.log(`Started on http://localhost:${PORT} (PID: ${process.pid})`); # HTTPINCOMINGMESSAGE (unknown stack trace) # HTTPINCOMINGMESSAGE (unknown stack trace) # TickObject (unknown stack trace)
问题原因
这是Windows平台下NodeJS 16.15.x版本的已知底层bug,和Express框架本身无关:该版本Node在Windows系统上的信号处理逻辑存在缺陷,当存在活跃的TCP Server监听句柄时,进程收到SIGINT、SIGTERM等终止信号后无法正确回收TCP句柄,直接进入卡死状态无法退出。
解决方案
按优先级从高到低选择即可:
- 升级NodeJS版本
直接升级到Node 16.16.0及以上的16.x补丁版本,或是升级到18.x、20.x等长期支持版本,该bug已经在后续官方版本中被修复,升级后无需修改业务代码即可解决问题。 - 代码层兼容(无法升级Node版本时使用)
显式监听进程终止信号,手动关闭app.listen()返回的服务实例,主动释放TCP句柄,修改后的代码示例:'use strict'; const express = require('express'); const app = express(); const PORT = 8040; const server = app.listen(PORT, () => { console.log(`Started on http://localhost:${PORT} (PID: ${process.pid})`); }); // 手动处理终止信号,主动释放服务句柄 const gracefulShutdown = () => { server.close(() => { process.exit(0); }); // 加1秒超时强制退出,避免意外的句柄泄漏导致卡死 setTimeout(() => process.exit(1), 1000).unref(); }; process.on('SIGTERM', gracefulShutdown); process.on('SIGINT', gracefulShutdown); - nodemon配置兼容
在nodemon配置文件中开启legacy监听模式,同时指定终止信号为SIGINT,部分场景下也能缓解卡死问题,配置示例:// nodemon.json { "signal": "SIGINT", "legacyWatch": true }
内容的提问来源于stack exchange,提问作者Marc
相关产品推荐
相关产品推荐

