TCPのコネクション確立時の"Too many open files"を発生させる

本エントリでは、大量のTCPのコネクションを張るクライアントプログラムを使って"Too many open files"を発生させ、の"Too many open files"の原因の理解を深めます。

## TCPのコネクション確立時の"Too many open files"

TCPのコネクション確立時の"Too many open files"は、Socket作成時にプロセスが利用可能なファイルディスクリプタ数の上限にあたった際に発生します。TCPクライアントで発生するケースとしては、大量のコネクションを張る負荷試験の負荷生成クライアント側で発生するケース等があります。

## TCPクライアントのコネクション確立時に"Too many open files"を発生させる

前述の通り、コネクション確立時に発生するの"Too many open files"は、Socket作成時にプロセスが利用可能なファイルディスクリプタ数の上限にあたった際に発生します。プロセスが利用可能なファイルディスクリプタ数は以下のulimitコマンドで確認可能です。

```
$ ulimit -n
1024
```

本エントリでは、ncコマンドでTCPサーバを起動し、多数のコネクションを張るTCPクライアントプログラムを実行し、"Too many open files"を発生させます。

### 1.前準備

本エントリでは、以下のLinuxコマンドを使用します。

* nc
 * TCPサーバとして使用します。-l,-k,-mオプションを併用して複数コネクションを受けるTCPサーバとして起動します。
* netstat,lsof
 * ソケットおよびファイルディスクリプタを確認するために使用します。
* strace
 * socketシステムコールの戻り値、errnoを確認するために使用します。

上記コマンドがインストールされていない場合、以下のコマンドでインストールします。

```
$ sudo yum -y install nc net-tools lsof
```

また多数のコネクションを張るTCPクライアントをperlで作成します。仕様は以下です。

* 引数に指定した数のコネクションを"127.0.0.1"の12345 portに張り維持する。
 * コネクションを張る際にエラーが発生した場合、標準エラー出力にエラー文言を出力する。
* 標準入力に"\n"を受けた後、コネクションをCloseする。

```tcpcon.pl 
#!/usr/bin/perl
use strict;
use IO::Socket;
my $num=shift || 1024;
my $host='127.0.0.1';
my $port=12345;
my @sock =();

for my $i (1 .. $num){
  $sock[$i] = new IO::Socket::INET(PeerAddr=>$host, PeerPort=>$port, Proto=>'tcp') or warn "$!";
}

print("PID($$) tried to create $num connections to $host:$port. Please type enter to contine.");

my $line = <STDIN>;

for my $s (@sock){
  if(defined($s)){
    close($s);
  }
}
```

### 2.正常系の確認

0.で準備したperlの動作確認および"Too many open files"が発生しない正常系の確認をします。

#### TCPサーバの起動

ncコマンドの-lオプションを使用し12345のport番号でTCPサーバを起動します。-k,-mオプションを使用して複数コネクションを受けることを可能とします。ここでは最大コネクション数を指定する-mオプションは1024を指定します。

```
 nc -lkm 1024 12345
```

#### TCPクライアント起動

別ターミナルから、tcpcon.plを512を引数に指定して実行します。

```
$ ./tcpcon.pl 512
PID(6015) tried to create 512 connections to 127.0.0.1:12345. Please type enter to contine.
```

tcpcon.plは引数で指定された数のコネクションを"127.0.0.1:12345"に張ります。Enterを押下すると、コネクションをCloseしてプロセスを終了します。ここでは、Enterを押下せずに次の確認に進みます。

#### TCPコネクションおよびファイルディスクリプタ数の確認

更に別ターミナルから上記tcpcon.plのPIDのコネクション数を確認します。512コネクションを確認することができます。

```
$ sudo netstat -anop | grep '127.0.0.1:12345' | grep 'perl' |  head -n 5
tcp        0      0 127.0.0.1:33116         127.0.0.1:12345         ESTABLISHED 6015/perl            off (0.00/0/0)
tcp        0      0 127.0.0.1:33096         127.0.0.1:12345         ESTABLISHED 6015/perl            off (0.00/0/0)
tcp        0      0 127.0.0.1:33690         127.0.0.1:12345         ESTABLISHED 6015/perl            off (0.00/0/0)
tcp        0      0 127.0.0.1:33672         127.0.0.1:12345         ESTABLISHED 6015/perl            off (0.00/0/0)
tcp        0      0 127.0.0.1:33662         127.0.0.1:12345         ESTABLISHED 6015/perl            off (0.00/0/0)
$ 
$ sudo netstat -anop | grep '127.0.0.1:12345' | grep 'perl' | wc -l
512
$ 
```

tcpcon.plプロセスの利用可能なファイルディスクリプタ数の上限を確認します。以下コマンドは6115をtcpcon.plプロセスのPIDに読み替えて下さい。

```
$ cat /proc/6115/limits | grep 'Max open files'
Max open files            1024                 1024                 files
$ 
```

プロセスが使用しているファイルディスクリプタが、標準入力、標準出力、標準エラー出力(3)とコネクションのsocket(512)の合計515でファイルディスクリプタ数の上限以内であることを確認します。

```
$ find /proc/6115/fd | xargs -I FILE readlink FILE | awk -F':' '{print $1}' | sort | uniq -c
      3 /dev/pts/1
    512 socket
$ 
```

ここまでで、"Too many open files"を発生させる準備が完了しました。tcpcon.plを実行しているターミナルでEnterを押下してプロセスを終了します。

```
$ ./tcpcon.pl 512
PID(6015) tried to create 512 connections to 127.0.0.1:12345. Please type enter to contine.
[Enter]
$
```

ncコマンドで起動したTCPサーバは起動したままにしておきます。

### 3.TCPのコネクション確立時に"Too many open files"を発生させる

2.の正常系に対して、"Too many open files"が発生する異常系のケースを確認します。

#### プロセスが利用可能なファイルディスクリプタ数の変更

以下のコマンドでプロセスが利用可能なファイルディスクリプタ数を変更します。ここでは512に変更します。

```
$ ulimit -n 512
$ 
```

#### TCPクライアント起動

ulimitでプロセスが利用可能なファイルディスクリプタ数を512としたターミナルから、tcpcon.plを512を引数に指定して実行します。

```
$ ulimit -n
512
$ ./tcpcon.pl 512
Too many open files at ./tcpcon.pl line 10.
Too many open files at ./tcpcon.pl line 10.
Too many open files at ./tcpcon.pl line 10.
PID(13262) tried to create 512 connections to 127.0.0.1:12345. Please type enter to contine.
```

"Too many open files"が3回発生します。

#### ファイルディスクリプタ、コネクションの確認

プロセスが利用可能なファイルディスクリプタ数(512) に対して、標準入力、標準出力、標準エラー出力(3)とコネクションのsocket(512)の合計515のファイルディスクリプタを利用しようとしたため、"Too many open files"が3回発生しています。
結果コネクション数は509(512-3)が確認されます。

```
$ cat /proc/13262/limits | grep 'Max open files'                                                                                      
Max open files            512                  512                  files     
$ 
$ find /proc/13262/fd | xargs -I FILE readlink FILE | awk -F':' '{print $1}' | sort | uniq -c
      3 /dev/pts/1
    509 socket
$ 
```

#### socketシステムコールのエラーコード

前述の手順と同様のstraceコマンドでsocket system callを確認します。socket system callにEMFILE (Too many open files)が返ることが確認可能です。

```
$ strace -Ttt -e network ./tcpcon.pl 512
...
03:40:33.973207 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = 511 <0.000319>
03:40:33.973965 connect(511, {sa_family=AF_INET, sin_port=htons(12345), sin_addr=inet_addr("127.0.0.1")}, 16) = 0 <0.000336>
03:40:33.974649 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = -1 EMFILE (Too many open files) <0.000096>
Too many open files at ./tcpcon.pl line 10.
03:40:33.976098 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = -1 EMFILE (Too many open files) <0.002012>
Too many open files at ./tcpcon.pl line 10.
03:40:33.978684 socket(AF_INET, SOCK_STREAM, IPPROTO_TCP) = -1 EMFILE (Too many open files) <0.000424>
Too many open files at ./tcpcon.pl line 10.
PID(14002) tried to create 512 connections to 127.0.0.1:12345. Please type enter to contine.
...
```

## プロセスが利用可能なファイルディスクリプタ数を指定する

プロセスが利用可能なファイルディスクリプタ数はユーザ毎に"/etc/security/limits.conf"または"/etc/security/limits.d"ディレクトリ以下のファイルに指定可能です。

以下はusernameユーザのプロセスが利用可能なファイルディスクリプタ数を20000に指定する場合の設定です。設定後、usernameユーザでsshログインすると値が有効になります。

```
username soft nofile 20000
username hard nofile 20000
```