DEBUG.WIKI
京东云;让 AI 进入真实业务;零售 · 物流 · 客服 · 硬件;查看方案 →
ZgoCloud:多地区部署,按需选节点;日本、美国、香港、德国
Evoxt;最高 6.0GHz CPU;高频云服务器 · 免费每周备份;查看配置 →

Linux 文件监听报 ENOSPC

Linux 开发服务或编辑器报文件监听 ENOSPC。定位同一用户占用的 inotify 监听项,缩小监听范围,再按需调整上限;说明远端、容器构建、旧版 Backstage 的适用条件及回退。

启动开发服务、打开大型工作区,或在 CI 中构建项目时,日志出现:

Error: ENOSPC: System limit for number of file watchers reached, watch '/path/to/project/app-config.yaml'

这里失败的是文件监听。清理磁盘、删掉依赖再安装,通常不会释放被其他进程占用的 inotify 监听资源。应先确认报错来自 watch,再查运行监听器的 Linux 环境、用户和配额。只有普通文件写入也报空间不足时,才同时检查磁盘与 inode。

容器构建步骤也可能创建文件监听器,不能仅凭“不是开发模式”排除 inotify 配额不足。应从失败日志确认触发监听的工具和文件,再区分资源限制与工具版本的监听行为。

下面适用于 Linux 上使用 inotify 的文件监听,包括运行在 Linux 远端的 VS Code 和 Node.js 工具。macOS、原生 Windows 使用不同的监听机制;应用版本引入的监听异常也需要单独核对。

配额属于用户,排错要在监听器所在环境做

Linux 的 fs.inotify.max_user_watches 限制同一真实用户 ID 可创建的监听项数量。编辑器、语言服务器和多个开发服务可能共同消耗这份配额。目录递归监听还需要覆盖子目录,并不总是一整个项目只用一个监听项。

在报错进程所在的 Linux 环境读取:

id -u
cat /proc/sys/fs/inotify/max_user_watches
cat /proc/sys/fs/inotify/max_user_instances

两个数分别是监听项上限和实例上限,不是当前使用量。inotify_add_watch() 返回 ENOSPC 的条件包括用户监听项达到上限,或内核无法分配所需资源;增加实例上限不能替代检查监听项。

如果日志其实是 EMFILE、too many open files,还要区分 inotify 实例数量和进程文件描述符数量:inotify_init() 在这两种限制下都可能返回 EMFILE。不要把所有监听故障都归到 max_user_watches,也不要只改 ulimit -n 来处理监听项耗尽。

VS Code 通过 SSH、WSL 或 Dev Containers 打开项目时,监听器在目标文件系统所在的环境运行。Windows 上的编辑器连到 Linux,需查 Linux 端的值;Docker Desktop 的 Linux 容器使用虚拟机内核,macOS 本机的设置不能直接替代。

找出占用监听项的进程

先保存编辑器中的修改,退出不用的工作区和开发服务,然后重试原命令。若恢复,说明故障可能与这些进程的监听占用有关。仍需找出占用来源,避免每次靠重启缓解。

Linux 3.8 及以后的 /proc/<PID>/fdinfo/ 会列出 inotify 监听项,每条以 inotify 开头的记录对应一个被监听的文件或目录。

有 Python 3 时,可以用下面的只读脚本查看当前用户下、可读取进程的监听项记录数:

python3 - <<'PY'
import os
from pathlib import Path

rows = []
for proc in Path('/proc').iterdir():
    if not proc.name.isdigit():
        continue
    try:
        status = (proc / 'status').read_text()
        uid_line = next(line for line in status.splitlines() if line.startswith('Uid:'))
        if int(uid_line.split()[1]) != os.getuid():
            continue
        entries = 0
        for fd in (proc / 'fdinfo').iterdir():
            try:
                entries += sum(line.startswith('inotify ') for line in fd.read_text().splitlines())
            except OSError:
                pass
        if entries:
            command = (proc / 'comm').read_text().strip()
            rows.append((entries, int(proc.name), command))
    except (OSError, ValueError, StopIteration):
        continue

print('PID\tWATCH_ENTRIES\tCOMMAND')
for entries, pid, command in sorted(rows, reverse=True):
    print(f'{pid}\t{entries}\t{command}')
PY

这些计数用于定位占用较多的进程。重复文件描述符或进程间共享实例可能重复计数,/proc 的权限限制和进程退出又可能导致遗漏,不能把所有行相加当作配额的精确使用量。在容器内运行时,也可能看不到其他容器或宿主机上的进程。

按 PID 核对具体任务,正常退出不再需要的那一个。不要执行 killall node:同一用户的其他开发服务也可能是 Node 进程。若释放无用监听后仍失败,或者监听项持续增长,再核对工具版本、重复启动和监听范围。

缩小不需要监听的目录

如果错误来自 VS Code 自身,可以在工作区 .vscode/settings.json 中合并以下设置,只排除项目确实不需要跟踪的生成目录和虚拟环境:

{
  "files.watcherExclude": {
    "**/.venv/**": true,
    "**/dist/**": true,
    "**/coverage/**": true
  }
}

已有设置要保留;目录名也应按项目调整。排除目录会影响 VS Code 对其中变化的感知,不应把正在编辑的源代码目录加入列表。可以先缩小监听范围,再按实际需求调整平台限制。VS Code 的默认设置已排除 node_modules 和部分 .git 目录。

这个设置控制 VS Code 的监听行为。终端里的 Vite、Webpack、测试工具可能有自己的监听器和忽略规则,不能以为修改编辑器设置后,它们也会停止监听。应根据错误日志中的工具名称,核对其对应版本的监听配置。

确实需要更多监听项时再提高上限

在确认没有重复进程、监听目录合理之后,由管理该 Linux 系统的人调整内核参数。共享机器上,这个值会改变各用户可使用的上限;提高上限允许更多内核资源被消耗,需要结合机器内存和实际工作区规模决定。

先记录原值,在同一个终端临时调整并重试:

previous_watches=$(cat /proc/sys/fs/inotify/max_user_watches)
printf '原值:%s\n' "$previous_watches"
sudo sysctl -w fs.inotify.max_user_watches=524288
cat /proc/sys/fs/inotify/max_user_watches

524288 是调整监听上限的参考值,不是所有机器都必须采用的值,也不是所有内核的硬上限。若当前值已更大,这条命令反而会降低上限,不应照抄;内存较小的环境可选择低一些的值。临时设置成功后,重新启动报错的监听任务,观察它能否完整运行,而不是只看 sysctl 返回成功。

确认有必要长期保留时,检查现有配置是否已定义同一参数:

grep -RnsE '^[[:space:]]*fs[./]inotify[./]max_user_watches' \
  /etc/sysctl.conf /etc/sysctl.d /run/sysctl.d \
  /usr/local/lib/sysctl.d /usr/lib/sysctl.d /lib/sysctl.d 2>/dev/null

以下适用于开机读取 /etc/sysctl.d/*.conf 的发行版。选择一个系统管理员维护的文件,例如 /etc/sysctl.d/90-local-inotify.conf;文件已存在时,先将原文件备份到独立位置,再合并设置,避免覆盖其他参数:

sudoedit /etc/sysctl.d/90-local-inotify.conf

文件内容加入已验证的值:

fs.inotify.max_user_watches=524288

显式加载这个文件,并读取实际值:

sudo sysctl -p /etc/sysctl.d/90-local-inotify.conf
cat /proc/sys/fs/inotify/max_user_watches

sysctl -p 可以只加载指定文件,便于控制这次变更范围。持久化还要核对发行版的开机加载方式和配置顺序;systemd 的 sysctl 配置按文件名排序,同一参数可能被后面的文件覆盖。Arch Linux、Ubuntu 24.10 及以后使用 sysctl.d 文件。不要重复追加多条相互冲突的设置。

Dockerfile 中修改失败,应检查构建宿主机

在 Dockerfile 中提高 max_user_watches 时,如果得到 Read-only file system,应检查构建宿主机的参数边界,不能靠再加一次 sudo 处理。

Docker 的 --sysctl 支持范围取决于参数是否已被内核命名空间隔离;不能把任意宿主机参数当成每个容器独立的配置。对于这类构建环境,应向实际运行构建器的 Linux 宿主机或虚拟机管理员核对限制,再从构建环境读回确认。不要给构建容器加 --privileged,或把 /proc/sys 改成可写来绕过边界。

托管 CI 不允许修改宿主机时,需减少同时运行的监听任务、缩小监听范围,或使用工具提供的无监听构建路径;这些措施是否可用取决于具体工具。轮询也应按工具文档启用,并评估 CPU、文件系统和刷新延迟的开销,不能把某个环境变量当作所有 Node 工具的通用开关。

使用旧版 Backstage 时,还应核对配置加载器版本。Backstage v1.19.0 中的 @backstage/config-loader 1.5.1 提供 watch 选项,可用 false 关闭配置监听。这里的 watch 是配置加载器 API 选项,不能直接当作 backstage-cli 的命令行参数,也不意味着所有 Backstage 构建都需要增加宿主机上限。升级应按项目依赖更新流程,在测试环境核对配置加载和构建结果。

修复后的检查与回退

重跑最初失败的命令,确认没有新的 ENOSPC,构建能结束或开发服务能持续运行。对依赖热更新的任务,保存一份实际使用的源文件,检查刷新确实发生;只确认进程启动,可能漏掉监听失效的问题。再查看监听项是否持续无界增长,若增长没有停止,应继续追查工具行为,而不是继续放大上限。

若仅作临时测试,在前面同一个终端里可恢复原值:

sudo sysctl -w "fs.inotify.max_user_watches=$previous_watches"

跨终端或重新登录后,变量不会自动保留,应使用先前记录的数值。已有进程仍需要较多监听项时,恢复较低上限可能使它们后续新增监听失败,需先协调受影响任务。

做过持久化配置的,还要恢复备份或移除本次新增的那一条设置,避免下次开机再次应用新值。移除配置文件不会立刻恢复当前内核值,仍需显式恢复并读回核对。撤销目录排除设置时,也要观察监听占用是否随之回升。