You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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的子进程问题

  1. 设置超时避免无限挂起:给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);
}
  1. 清理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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.15 06:36:48