
spawnSync() 跑完了,标准输出却带着 ENOBUFS 错误返回。更值得先查的是父进程的同步缓冲区是否撞上 maxBuffer 限制。把上限调高能放宽缓冲空间,也会提高单次调用可能占用的内存。
这次测试使用本机 Node v26.8.1 和 Node 自带的子进程接口。子进程只向 stdout 写固定字符串,不访问文件或网络。实验把 maxBuffer 设为 32 字节,观察小输出与大输出的差别;数字是测试设置,不是 Node 的默认值。
输出小于上限时,返回值就是缓冲内容
小输出实际返回 status=0,12 字节全部收到;大输出则返回 ENOBUFS、status=null。这次快速写入的 128 字节仍全部出现在 stdout,说明不能把 maxBuffer: 32 简化理解成「最多拿到 32 字节」。超过上限会进入错误路径,缓冲内容是否完整不该作为成功依据。
实测原始结果:
{"n":12,"status":0,"error":null,"bytes":12}
{"n":128,"status":null,"error":"ENOBUFS","bytes":128}
maxBuffer 是缓冲区限制,不是子进程输出长度的严格截断承诺。最终能看到多少输出,还会受操作系统调度和子进程退出时机影响,数据到达的批次也有关系。调用者应先检查 error 和 status,错误时把标准输出当作诊断信息,而不是完整业务结果。
spawnSync() 的错误结果里,error 与 status 应一起检查。启动失败和缓冲区超限不是同一种情况;进程被信号终止时,则要另看 signal。只用 status === 0 判断成功,会把一些非成功路径混在一起。业务代码可以先记录 error.code 与 signal,再按具体命令决定是否重试。缓冲区超限通常不是重试能解决的问题,除非输出规模本身会变化且有明确上界。
同步 API 会让当前 Node 线程等待,并把输出留在内存。日志工具若输出几百 MB,单纯提高 maxBuffer 只是把可能的内存峰值推高。若只需要摘要,最好让子程序自己限量;若必须处理完整内容,就用异步 spawn() 监听 data,边到边消费,或把输出流接到文件。
异步流式读取也有取舍
spawn() 不会把全部输出先装进一个字符串,但消费者仍须处理背压。data 事件里把每个 chunk 都累加到数组,最后 Buffer.concat(),同样会把总量完整留在内存。真正有界的做法是把数据写向有背压的 Writable,或逐块解析后只留下必要字段。
缓冲上限也要按两条流分别考虑。一个调用同时收集 stdout 和 stderr,诊断日志再多一点,就可能让原本很短的标准输出命令失败。若 stderr 仅用于错误提示,可把它导向文件或改用异步流读取;不要为了「看起来安全」把它静默丢弃,否则错误原因也会一起消失。同步方法还会阻塞当前事件循环,Web 服务里直接等待外部命令,其他请求也会被拖住。命令自身是否很快结束,并不能消除这段等待;主线程要等子进程退出后才继续执行后面的 JavaScript。
我不会把 maxBuffer 提到一个很大的数字当作默认修复。同步子进程适合短小且上界明确的命令;日志量不受控、命令运行时间较长,或需要持续处理输出时,改用流式接口更稳。还要记得分别处理 stdout 与 stderr,否则一个安静的 stdout 也挡不住 stderr 把缓冲区填满。