Nodemon卡在‘restarting due to changes...’无法重启服务器排查
我之前维护旧Node项目时也碰到过几乎一模一样的Nodemon重启卡住问题,结合你的场景来看,核心问题大概率是旧进程无法正常终止——Nodemon检测到文件变化后会尝试重启,但旧进程因为残留的资源占用(数据库连接池、未关闭的子进程)无法退出,导致Nodemon一直等待旧进程终止,最终卡住。
具体原因拆解
1. MySQL连接池未优雅关闭
mysql.createPool创建的连接池默认会保持持久化连接,当Nodemon发送终止信号给旧进程时,如果没有主动关闭连接池,Node进程会因为持有活跃的数据库连接而无法正常退出。在Node 7.x/8.x版本中,这种资源泄漏导致进程无法退出的情况尤为常见。
2. child_process.execFileSync的子进程残留
虽然execFileSync是同步执行的,但如果你的Java工具在执行过程中会fork后台子进程,这些子进程不会随着父进程的终止而自动退出,会变成孤儿进程。Nodemon无法识别并杀掉这些孤儿进程,旧进程的资源被持续占用,进而导致重启流程卡住。另外,如果execFileSync调用没有设置超时,一旦Java工具执行异常挂起,也会导致旧进程无法退出。
针对性解决办法
一、给MySQL连接池添加优雅退出逻辑
在你的src/index.js中,监听进程的终止信号,主动关闭连接池后再退出进程:
// 假设你的连接池实例是mysqlPool const mysql = require('mysql'); const mysqlPool = mysql.createPool({ /* 你的连接配置 */ }); // 处理SIGINT(Ctrl+C)和SIGTERM(Nodemon发送的终止信号) process.on('SIGINT', async () => { try { await new Promise((resolve, reject) => { mysqlPool.end((err) => { if (err) reject(err); else resolve(); }); }); console.log('MySQL连接池已关闭,进程即将退出'); process.exit(0); } catch (err) { console.error('关闭连接池失败:', err); process.exit(1); } }); process.on('SIGTERM', async () => { // 重复上面的逻辑,确保Nodemon发送的SIGTERM信号也能触发连接池关闭 try { await new Promise((resolve, reject) => { mysqlPool.end((err) => { if (err) reject(err); else resolve(); }); }); console.log('MySQL连接池已关闭,进程即将退出'); process.exit(0); } catch (err) { console.error('关闭连接池失败:', err); process.exit(1); } });
注意:Node 7.x支持async/await,但需要确保你的Babel配置正确转译了这部分语法,或者改用回调方式处理连接池关闭。
二、修复execFileSync的子进程问题
- 设置超时避免无限挂起:给
execFileSync添加timeout选项,防止Java工具执行异常导致进程卡住:
const { execFileSync } = require('child_process'); try { // 设置10秒超时,根据你的Java工具执行时间调整 const result = execFileSync('java', ['-jar', 'your-tool.jar'], { timeout: 10000 }); } catch (err) { console.error('Java工具执行超时或失败:', err); }
- 清理Java工具的后台子进程:如果Java工具会fork后台进程,你需要在进程退出时追踪并杀掉这些子进程。可以通过
execFile(异步版本)获取子进程PID,然后在终止信号触发时杀掉它:
const { execFile } = require('child_process'); let javaChildProcess = null; // 改用execFile异步执行,保存子进程实例 function runJavaTool() { javaChildProcess = execFile('java', ['-jar', 'your-tool.jar'], (err, stdout, stderr) => { // 处理执行结果 }); } // 在进程终止时杀掉Java子进程 process.on('SIGINT', () => { if (javaChildProcess) { javaChildProcess.kill('SIGTERM'); } // 加上之前的连接池关闭逻辑 // ... });
三、优化Nodemon配置
修改你的nodemon.js,确保发送正确的终止信号并强制崩溃退出:
module.exports = { watch: ['src'], env: { NODE_ENV: 'development' }, // 指定用SIGTERM信号终止进程,确保旧进程能收到退出通知 signal: 'SIGTERM', // 进程崩溃时强制退出,避免卡住 exitcrash: true, exec: 'node index.js' };
四、验证进程退出状态
重启前可以用命令ps aux | grep node查看当前Node进程,修改文件后再次执行该命令,检查旧进程是否已经消失。如果旧进程仍然存在,说明你的终止信号处理逻辑有问题,需要进一步排查资源泄漏点。
内容的提问来源于stack exchange,提问作者Christopher Francisco

