too many open filesエラーが出たときにlsofコマンドで確認する

概要

too many open filesエラーが出たときにlsofコマンドで確認するときの手順を説明する機会があったのでまとめる。

環境

node -v
v16.16.0
docker -v
Docker version 20.10.17, build 100c701

用意するもの

Dockerfile
file.txt
index.js

Dockerfile

FROM node:16.16.0-bullseye-slim
COPY . .
RUN apt update && apt install -y lsof procps linux-perf
CMD ["node","index.js"]

サンプルプログラム index.js

const fs = require('fs');

const openFileAndDontClose = () => {
  fs.open('file.txt', 'r', (err, fd) => {
    console.log(`fd: ${fd}`)
    console.log(`err: ${err}`)
  });
}

setInterval(openFileAndDontClose, 1000)

1秒ごとにfile.txtを開き続ける。

検証

docker buildx build -t test .
docker run --name test test
docker exec -it test /bin/bash

nodeプロセスのPIDを確認

ps aux            
USER       PID %CPU %MEM    VSZ   RSS TTY      STAT START   TIME COMMAND
root         1  0.0  0.0   4100  3340 pts/0    Ss   13:41   0:00 /bin/bash
root        10  0.1  0.4 352596 33644 pts/0    Sl+  13:41   0:00 node index.js
root        21  0.0  0.0   4100  3520 pts/1    Ss   13:41   0:00 /bin/bash
root        34  0.0  0.0   6700  3012 pts/1    R+   13:42   0:00 ps aux
ulimit -Sn
1048576
ulimit -Hn
1048576

ファイルディスクリプタの確認

/proc/PID/fd

ls -l /proc/1/fd
total 0
lrwx------ 1 root root 64 Jul 16 14:00 0 -> /dev/null
l-wx------ 1 root root 64 Jul 16 14:00 1 -> 'pipe:[56376]'
lr-x------ 1 root root 64 Jul 16 14:00 10 -> 'pipe:[55593]'
l-wx------ 1 root root 64 Jul 16 14:00 11 -> 'pipe:[55593]'
lrwx------ 1 root root 64 Jul 16 14:00 12 -> 'anon_inode:[eventfd]'
lrwx------ 1 root root 64 Jul 16 14:00 13 -> 'anon_inode:[eventpoll]'
lr-x------ 1 root root 64 Jul 16 14:00 14 -> 'pipe:[50868]'
l-wx------ 1 root root 64 Jul 16 14:00 15 -> 'pipe:[50868]'
lrwx------ 1 root root 64 Jul 16 14:00 16 -> 'anon_inode:[eventfd]'
lr-x------ 1 root root 64 Jul 16 14:00 17 -> /file.txt
lr-x------ 1 root root 64 Jul 16 14:00 18 -> /dev/null
lr-x------ 1 root root 64 Jul 16 14:00 19 -> /file.txt
l-wx------ 1 root root 64 Jul 16 14:00 2 -> 'pipe:[56377]'
lr-x------ 1 root root 64 Jul 16 14:02 20 -> /file.txt
lr-x------ 1 root root 64 Jul 16 14:02 21 -> /file.txt
lr-x------ 1 root root 64 Jul 16 14:02 22 -> /file.txt
lr-x------ 1 root root 64 Jul 16 14:02 23 -> /file.txt
...

23 -> /file.txt
fdの番号 -> 開いている対象

lsofコマンド

lsof -p PID
# もしくは
# lsof -c プロセス名
lsof -p 1
COMMAND PID USER   FD      TYPE DEVICE SIZE/OFF    NODE NAME
node      1 root  cwd       DIR  0,142     4096  270825 /
node      1 root  rtd       DIR  0,142     4096  270825 /
node      1 root  txt       REG  0,142 81180048 2890255 /usr/local/bin/node
node      1 root  mem       REG  254,1          2890255 /usr/local/bin/node (path dev=0,142)
node      1 root  mem       REG  254,1          2885690 /lib/x86_64-linux-gnu/libc-2.31.so (path dev=0,142)
node      1 root  mem       REG  254,1          2885735 /lib/x86_64-linux-gnu/libpthread-2.31.so (path dev=0,142)
node      1 root  mem       REG  254,1          2885704 /lib/x86_64-linux-gnu/libgcc_s.so.1 (path dev=0,142)
node      1 root  mem       REG  254,1          2885711 /lib/x86_64-linux-gnu/libm-2.31.so (path dev=0,142)
node      1 root  mem       REG  254,1          2886466 /usr/lib/x86_64-linux-gnu/libstdc++.so.6.0.28 (path dev=0,142)
node      1 root  mem       REG  254,1          2885698 /lib/x86_64-linux-gnu/libdl-2.31.so (path dev=0,142)
node      1 root  mem       REG  254,1          2885678 /lib/x86_64-linux-gnu/ld-2.31.so (path dev=0,142)
node      1 root    0u      CHR    1,3      0t0       5 /dev/null
node      1 root    1w     FIFO   0,11      0t0   56376 pipe
node      1 root    2w     FIFO   0,11      0t0   56377 pipe
node      1 root    3u  a_inode   0,12        0   12340 [eventpoll]
node      1 root    4r     FIFO   0,11      0t0   50866 pipe
node      1 root    5w     FIFO   0,11      0t0   50866 pipe
node      1 root    6r     FIFO   0,11      0t0   50867 pipe
node      1 root    7w     FIFO   0,11      0t0   50867 pipe
node      1 root    8u  a_inode   0,12        0   12340 [eventfd]
node      1 root    9u  a_inode   0,12        0   12340 [eventpoll]
node      1 root   10r     FIFO   0,11      0t0   55593 pipe
node      1 root   11w     FIFO   0,11      0t0   55593 pipe
node      1 root   12u  a_inode   0,12        0   12340 [eventfd]
node      1 root   13u  a_inode   0,12        0   12340 [eventpoll]
node      1 root   14r     FIFO   0,11      0t0   50868 pipe
node      1 root   15w     FIFO   0,11      0t0   50868 pipe
node      1 root   16u  a_inode   0,12        0   12340 [eventfd]
node      1 root   17r      REG  0,142        0 3807111 /file.txt
node      1 root   18r      CHR    1,3      0t0       5 /dev/null
node      1 root   19r      REG  0,142        0 3807111 /file.txt
node      1 root   20r      REG  0,142        0 3807111 /file.txt
node      1 root   21r      REG  0,142        0 3807111 /file.txt
...
lsof -p 1
COMMAND PID USER   FD      TYPE DEVICE SIZE/OFF    NODE NAME
...
node      1 root   20r      REG  0,142        0 3807111 /file.txt
  • FDは20番をreadで開いているという意味
  • TYPEのREGはregular file
  • NAMEは開いている対象

今回のプログラムはfile.txtを開き続けるので、file.txtが多く表示される。

総数だけ見たいときは下記

lsof -p 1 | wc -l

ulimitを変更する

docker run --name test --ulimit nofile=100:100 test
ulimit -Sn 
100
ulimit -Hn
100

上限を30にすると29まで使った次に、too many open filesのエラーが出ることを確認

docker run --name test --ulimit nofile=30:30 test
fd: 17
err: null
fd: 19
err: null
fd: 20
err: null
fd: 21
err: null
fd: 22
err: null
fd: 23
err: null
fd: 24
err: null
fd: 25
err: null
fd: 26
err: null
fd: 27
err: null
fd: 28
err: null
fd: 29
err: null
fd: undefined
err: Error: EMFILE: too many open files, open 'file.txt'

まとめ

too many open filesのエラーが出たとき、どのプロセスが、なんのファイルを開こうとしているのか確認する手順は下記

  1. ps auxでプロセス番号確認
  2. lsof -p PIDでファイルの上表を表示
  3. NAME欄で多く表示されているものを確認する

対応として、

  • プログラムで不具合はないか確認
  • ulimitで開くことのできるファイルの上限を上げる

参考情報

man lsof
...
  FD is followed by one of these  characters,  describing  the
                  mode under which the file is open:

                       r for read access;
                       w for write access;
                       u for read and write access;
                       space if mode unknown and no lock
                            character follows;
                       `-' if mode unknown and lock
                            character follows.
...
man lsof
...
TYPE       is  the  type  of  the node associated with the file - e.g.,
                  GDIR, GREG, VDIR, VREG, etc...
...
                  or ``REG'' for a regular file;
...

https://nodejs.org/dist/latest-v16.x/docs/api/fs.html#fsopenpath-flags-mode-callback