Node.js的child_process.exec默认超时是60秒,而非无限;其内部timeout设为0会触发60秒后强制终止子进程的fallback机制。

Node.js 的 child_process.exec 默认超时是 0?不,是 60 秒
很多人以为 exec 不设 timeout 就是“永不超时”,其实不然。Node.js 内部默认把 timeout 设为 0,但这个 0 并非“不限制”,而是触发一个 fallback 行为:当子进程卡住、stdout/stderr 无输出且未退出时,Node 会在 60 秒后强制 kill 掉它,并抛出 Error: Command failed: timeout 或类似 spawn ETIMEDOUT 的错误。
如果你的 PHP 脚本要跑 90 秒(比如导出大数据报表、处理图像队列),不显式设置 timeout,大概率在第 61 秒被砍断。
实操建议:
- 始终显式传入
timeout选项,哪怕设为300000(5 分钟) - 注意单位是毫秒,不是秒 ——
{ timeout: 120000 }是 2 分钟 - 配合
killSignal: 'SIGTERM'更友好,避免 PHP 进程残留
execFile 比 exec 更稳,尤其调用 PHP CLI
exec 会经过 shell 解析(如 /bin/sh -c),容易因空格、特殊字符或环境变量导致意外截断或注入;而 execFile 直接执行二进制,更可控、启动更快、也更少受 shell 超时策略干扰。
立即学习“PHP免费学习笔记(深入)”;
调用 PHP 脚本的典型写法:
const { execFile } = require('child_process');
execFile('php', ['script.php', '--arg=value'], {
timeout: 180000,
maxBuffer: 10 * 1024 * 1024 // 防止大输出撑爆内存
}, (err, stdout, stderr) => {
if (err && err.code === 'ETIMEOUT') {
console.error('PHP 脚本执行超时');
}
});
关键点:
- 不要拼接命令字符串,把参数拆成数组传给
execFile -
maxBuffer默认只有 1MB,PHP 输出大量日志或 JSON 时极易触发stderr maxBuffer exceeded错误 - PHP 脚本里记得用
flush()+ob_flush()主动推送进度(如果需要流式反馈)
PHP 端也要防“假死”:忽略用户连接中断,但别真卡住
Node.js 超时 kill 子进程后,PHP 进程未必立刻终止——尤其用了 ignore_user_abort(true) 或数据库长事务时,它可能还在后台跑,造成资源堆积。
所以光调大 Node 端 timeout 不够,PHP 侧得配合:
- 用
set_time_limit(0)解除 PHP 自身执行时间限制(但仅对 CLI 有效) - 避免
sleep()或阻塞 I/O(如没设 timeout 的 cURL 请求) - 数据库查询加
mysqli::options(MYSQLI_OPT_CONNECT_TIMEOUT, 5)类似配置,防止卡在连接层 - 检查是否启用了
opcache.enable_cli=1,避免重复编译开销
超时值不是越大越好:监控比硬扛更重要
把 timeout 设成 30 分钟看似保险,但掩盖了真实问题:是 PHP 逻辑太慢?IO 瓶颈?还是数据量突增?长期靠拉高阈值扛,反而难定位根因。
更务实的做法:
- 对不同用途的 PHP 脚本分级设 timeout(例如导出设 120s,校验设 10s)
- 在 Node 层记录实际耗时:
const start = Date.now(); ... console.log(`PHP done in ${Date.now() - start}ms`) - 超时发生后,捕获
stderr并打日志——PHP 常在崩溃前输出 warning 或 PDO 异常信息 - 考虑用消息队列(如 Bull)替代直连调用,把长任务异步化
真正棘手的往往不是 timeout 数值本身,而是 PHP 进程卡在哪一层:是等 MySQL 锁?等 Redis 响应?还是 fopen() 读一个 NFS 挂载的慢盘?这些都得靠日志+超时上下文一起看。











