Module version mismatch 错误排查

node.js 应用启动时出现以下错误:

Error: Cannot find module '../build/Debug/addon'
    at Function.Module._resolveFilename (module.js:339:15)
    at Function.Module._load (module.js:290:25)
    at Function._load (/usr/lib/node_modules/pm2/node_modules/pmx/lib/transaction.js:62:21)
    at Module.require (module.js:367:17)
    at require (internal/module.js:16:19)
    at Object.<anonymous> (node_modules/heapdump/lib/main.js:18:15)
    at Module._compile (module.js:413:34)
    at Object.Module._extensions..js (module.js:422:10)
    at Module.load (module.js:357:32)
    at Function.Module._load (module.js:314:12)

改了一下 heapdump/lib/main.js:18:15 附近的代码,输出了真正的错误信息:

Error: Module version mismatch. Expected 47, got 46.

根据 node_version.h 中 NODE_MODULE_VERSION 的定义,46 对应 Node.js v4.0.0,47 对应 Node.js v5.0.0。 应该是编译 heapdump 模块使用的 Node.js 版本和运行时 Node.js 版本不一致,编译时我通过 $PATH 环境变量,将 Node.js v4.2.3 置为默认的 Node.js 版本了。

$ /usr/bin/node --version 
v5.7.0
$ node --version
v4.2.3

我使用的是 pm2 做为进程管理器,cluster 模式运行 node.js 应用,pm2 后台进程使用的是默认版本的 Node.js 版本(v5.7.0)启动,应该是 pm2 也使用同样的 Node.js 版本(v5.7.0)来运行应用,执行 pm2 save 后,~/.pm2/dump.pm2 中我的应用的 $PATH 是正确的,已经将 Node.js v4.2.3 置为默认的 Node.js 版本,不知为何 pm2 并未采用。

pm2 分为前端命令和后端 daemon 两部分,真正的操作都是由 daemon 来施行,当我们使用 pm2 start 来启动 app 时,只是把命令通过 unix socket 传递给了 daemon,一个合理的猜想是 pm2 命令并没有把当前 shell 的 $PATH 传递给 daemon,或者是 daemon 创建 app 进程时传递过来的 $PATH 设置未生效。

查看当前的 pm2 版本:

$ ps aux | grep PM2 | grep -v grep 
tangxin+ 17538  0.1  0.4 1185564 32020 ?       Ssl  2月25   9:29 PM2 v0.14.5: God Daemon

通过 –interpreter 选项启动应用时指定 Node.js v4.2.3: –interpreter=/usr/local/node-v4.2.3/bin/node

通过 pm2 delete 删除应用后再 start 应用,结果还是一样的错误,查看应用实际使用的 node 版本:

$ ls -la /proc/22387/exe
lrwxrwxrwx 1 tangxinfa tangxinfa 0 3月   2 14:00 /proc/22387/exe -> /usr/bin/node

使用的还是系统默认的 Node.js 版本 v5.7.0。

经过测试,可以确认:

通过 –interpreter 指定其它 node 版本,在 cluster 模式下无效,fork 模式下有效。

参见相关 Issues:

查看 pm2 与 node.js 的源代码进一步确认该问题:

  • pm2 调用 cluster.fork 创建工作进程

    引用自 pm2/lib/God/ClusterMode.js

    cluster.fork({pm2_env: JSON.stringify(env_copy)})
    
  • cluster.fork 调用 child_process.fork 创建工作进程

    引用自 node/lib/cluster.js

    return fork(cluster.settings.exec, cluster.settings.args, {
      env: workerEnv,
      silent: cluster.settings.silent,
      execArgv: execArgv,
      gid: cluster.settings.gid,
      uid: cluster.settings.uid
    });
    

    根据 child_process.fork 的实现(见 node/lib/child_process.js),由于未传入 execPath 选项,会使用 process.execPath 的值,也就是会使用 pm2 后台进程的 node 可执行程序路径来创建工作进程。

应该可以通过指定不同的 $PM2_HOME 环境变量,跑多套 pm2,各个 pm2 使用不同版本的 Node.js,多个 cluster 模式的 pm2 应用也就会使用不同版本 Node.js。