bigywater.tech
常见报错 · 30

Node.js EMFILE: too many open files:ulimit -n 256 只能打开 245 个,console.log 自己也会崩,createReadStream 与 fs.watch 才会触发

“EMFILE: too many open files” 常见的建议是调高 ulimit,或者用 graceful-fs。这篇在本机把上限降到 256 复现它:看能打开多少个、哪些读文件的写法真的会触发、日志本身会不会崩溃、fs.watch 为什么也会触发,以及限制并发的效果。

245 / 256
ulimit -n 256 时实际能打开的文件数:node 自己已经占了 11 个
122
同一进程里服务端加客户端,ulimit -n 256 时能建立的连接数(每个连接占 2 个描述符)
64
把 createReadStream 的同时数限制到 64 后,3000 个文件读完不再 EMFILE

上限是什么

实测 · 本机执行输出
  当前 shell 的 ulimit -n(软)= 1048576,sysctl kern.maxfilesperproc = 61440
  ulimit -n 256(退出码 0):
      第一个失败:EMFILE: too many open files, open '<tmp>/files/f245.txt'
      code = EMFILE | errno = -24 | syscall = open
      成功打开 245 个,失败 2755 个
  同样的循环,但用 console.log 打印错误(stdout 是管道时它要现建一个套接字,也要描述符)(退出码 1):
      (libuv) kqueue(): Too many open files
      (libuv) kqueue(): Too many open files
      node:net:713
            throw new ErrnoException(err, 'open');
            ^
      Error: open EMFILE
          at new Socket (node:net:713:13)

每个进程同时能打开的描述符有上限,node 自己先占了十来个。

套接字也算

实测 · 本机执行输出
  ulimit -n 256(同一个进程里既是服务端又是客户端,每个连接占 2 个描述符)(退出码 0):
      客户端成功连接 122 个后失败
      open EMFILE
      code = EMFILE | syscall = open
      (libuv) kqueue(): Too many open files

连接数或连接泄漏同样会触发 EMFILE;而且描述符用尽时 console.log 可能连错误都打不出来。

哪些写法会触发

实测 · 本机执行输出
  promises.readFile:全部成功;0.046 s
  readFile-callback:全部成功;0.044 s
  createReadStream:失败,第一个错误 code = EMFILE;0.047 s
  createReadStream(并发 64):全部成功;0.060 s
  readFileSync:全部成功;0.052 s

自己管生命周期的 createReadStream、fs.open 一次建太多会触发;限制并发就好。

调高上限

实测 · 本机执行输出
  ulimit -n 64   : 成功 53 个,失败 2947 个,上限与成功数之差 11
  ulimit -n 128  : 成功 117 个,失败 2883 个,上限与成功数之差 11
  ulimit -n 256  : 成功 245 个,失败 2755 个,上限与成功数之差 11
  ulimit -n 1024 : 成功 1013 个,失败 1987 个,上限与成功数之差 11
  ulimit -n 4096 : 3000 个全部成功(上限够用)

在启动 node 之前设置;成功数总比上限少 node 自己占的那部分。

fs.watch

实测 · 本机执行输出
  ulimit -n 256,监视 1000 个文件:成功监视 245 个后失败:EMFILE: too many open files, watch '<tmp>/files/f245.txt' | code = EMFILE
  ulimit -n 10240,监视 1000 个文件:监视了 1000 个文件,没有错误

在 macOS 上每个监视的文件一个描述符。

分步演示

文件描述符
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64;报错文本都是真实复现时原样捕获的(命令行工具统一用 LC_ALL=C 取英文原文,路径换成 );所有服务都是本机上自己起的、只监听 127.0.0.1,不访问外网;计时用 time.perf_counter,数字每次运行会有小幅抖动。本篇用 Node v26.12.0;用 sh -c "ulimit -n N; node 脚本" 给单个 node 进程设置软限制;文件是实验目录里 3000 个小文本文件,实验结束后删除
    • 没有验证:graceful-fs 的效果(没有联网安装);Linux 上的行为,尤其是 fs.watch 用 inotify 时不占每文件一个描述符,以及 ENOSPC 达到 inotify watcher 上限的另一类报错;容器与 systemd 里设置上限的方式;fs.promises.readFile 一次读 3000 个文件为什么没有失败(只测到现象,没有查实现);ulimit -n 超出 kern.maxfilesperproc 后实际生效的值
    京ICP备2026067502号-1京公网安备11010502063487号