Nodejs子进程执行脚本的问题

Nodejs子进程执行脚本的问题

一个比较奇怪的问题 我用 child_process.fork(’./controllers/run-task.js’,[results.saveTask._id.toString(),true],{silent:true}); 来跑一个任务。run-task里面主要是调用svn命令(用了svn-interface模块)去export文件到本地,然后进行一些处理。run-task使用async.eachLimit来控制export的并发,目前是5个。 我发现任务比较多的时候比如有150个文件需要export时,通常只export到85个左右,程序就停在那里了,没有任何报错异常。 查看进程 子进程还在 QQ截图20141105110220.jpg 我如果在命令行 直接跑这个脚本 /usr/bin/nodejs ./controllers/run-task.js 5459803aa123c7080669e7c2 true 则一切正常,所有150个文件都导出完成了。 child_process.fork有什么限制么?还是我用的不对? 补充: 如果在文件比较少时,比如20个。调用child_process.fork来跑任务时可以完成的。


5 回复

Nodejs子进程执行脚本的问题

背景描述

在使用Node.js的child_process.fork方法来执行子进程时,遇到了一个奇怪的问题。当任务数量较多时(例如150个文件需要导出),程序通常只能完成部分任务(例如85个)后便停止运行,而没有任何错误或异常提示。而在命令行直接运行该脚本时,则能顺利完成所有任务。

问题现象

  • 使用child_process.fork执行子进程时,任务无法完全完成。
  • 没有任何错误或异常提示。
  • 在任务较少时(例如20个文件),程序可以正常完成任务。

示例代码

const { fork } = require('child_process');
const results = {
    saveTask: {
        _id: '5459803aa123c7080669e7c2'
    }
};

// 使用 child_process.fork 方法启动子进程
const child = fork('./controllers/run-task.js', [
    results.saveTask._id.toString(),
    true
], {
    silent: true // 禁止子进程输出到父进程的标准输出流
});

// 监听子进程事件
child.on('message', (msg) => {
    console.log('子进程消息:', msg);
});

child.on('exit', (code, signal) => {
    console.log(`子进程退出,退出码: ${code}, 信号: ${signal}`);
});

可能的原因与解决方案

  1. 缓冲区溢出

    • silent: true 可能导致子进程的输出没有被正确捕获,从而造成缓冲区溢出。
    • 解决方案:将silent设置为false或者使用stdoutstderr来捕获输出。
  2. 资源限制

    • 子进程可能由于系统资源限制(如文件描述符、内存等)而无法继续运行。
    • 解决方案:检查系统资源使用情况,并适当调整资源限制。
  3. 异步处理问题

    • async.eachLimit 的并发控制可能存在问题,尤其是在大量任务时。
    • 解决方案:确保每个任务都能正确完成,可以添加更多的错误处理逻辑。

示例修改后的代码

const { fork } = require('child_process');
const { execFile } = require('child_process');

const results = {
    saveTask: {
        _id: '5459803aa123c7080669e7c2'
    }
};

// 使用 child_process.execFile 方法启动子进程
execFile('/usr/bin/nodejs', ['./controllers/run-task.js', results.saveTask._id.toString(), true], (error, stdout, stderr) => {
    if (error) {
        console.error(`执行出错: ${error}`);
        return;
    }
    console.log(`stdout: ${stdout}`);
    console.error(`stderr: ${stderr}`);
});

通过上述修改,可以更好地捕获子进程的输出,有助于排查问题。同时,可以考虑调整资源限制或增加错误处理逻辑以确保任务能够完整执行。


额,知道哪的问题,{silent:true}参数引起的。去掉之后就好了。 不过根本原因还是不懂。。。

查询了很多资料,终于搞明白了 原因是slient=true时,子进程的stdout,stdin,stderr都会pipe到父进程,当子进程的stdout(我的例子里就是日志输出)超过了缓冲区大小,子进程就会暂停,直到缓冲区的stdout数据被读走。 而我只是创建了子进程执行脚本,就没有管它了。所以会出现导出到一定数量的文件(本质是日志输出到一定大小)就停那里了。

学习了!

在Node.js中使用child_process.fork()创建子进程时遇到的问题通常是由于子进程的输出缓冲区限制或资源管理问题。默认情况下,子进程的标准输出和标准错误输出会被缓存,如果这些输出没有被及时读取,会导致子进程阻塞。

根据你的描述,当使用child_process.fork()来运行任务时,任务会卡住,并且没有抛出任何异常。而在命令行直接运行脚本时,则能够顺利完成所有任务。

可能的原因

  1. 缓冲区满:如果子进程的输出没有被及时读取,输出缓冲区可能会溢出,导致子进程挂起。
  2. 资源限制:可能在子进程中存在资源限制,例如打开的文件描述符数量过多。
  3. 异步控制问题:在子进程中使用async.eachLimit时,可能因为某些原因未能正确处理异步操作。

解决方法

  1. 读取子进程的输出: 确保你读取了子进程的stdoutstderr,避免输出缓冲区溢出。

    const { fork } = require('child_process');
    const child = fork('./controllers/run-task.js', [results.saveTask._id.toString(), true], { silent: false });
    
    child.stdout.on('data', (data) => {
      console.log(`stdout: ${data}`);
    });
    
    child.stderr.on('data', (data) => {
      console.error(`stderr: ${data}`);
    });
    
  2. 调整子进程的配置: 如果你需要更多的文件描述符,可以通过设置环境变量来调整:

    const child = fork('./controllers/run-task.js', [results.saveTask._id.toString(), true], {
      env: {
        ...process.env,
        FILE_DESCRIPTORS: '4096'
      }
    });
    
  3. 检查子进程的逻辑: 在子进程中确保所有的异步操作都被正确处理。例如,在run-task.js中,确保所有任务都被正确提交给async.eachLimit

示例代码

// run-task.js
const async = require('async');
const svnInterface = require('svn-interface');

module.exports = function(id, flag) {
  async.eachLimit([/* your task list */], 5, (task, callback) => {
    svnInterface.export(task.url, task.path, (err) => {
      if (err) return callback(err);
      console.log(`Exported ${task.path}`);
      callback();
    });
  }, (err) => {
    if (err) {
      console.error(`Error during export: ${err.message}`);
      process.exit(1);
    }
    console.log('All tasks completed.');
  });
};

通过以上方法,你应该能够解决子进程卡住的问题。

回到顶部