Help us understand the problem. What is going on with this article?

Listen backlog

More than 5 years have passed since last update.

きっかけ

この記事を投稿しようと思ったきっかけですが、nginx+gnicorn+Djangoで開発をする機会がありまして、開発も終盤にさしかかりパフォーマンスチェックをしている際に問題が起きました。

abコマンドでベンチマークを取っていたんですが、各設定は

#nginx
worker_processes  2;
events {
    worker_connections  1024;
}

#gunicorn
bind = 'unix:/tmp/gunicorn.sock'
backlog = 2048
workers = 2
worker_class = 'sync'

abはこんな感じです

ab -n 5000 -c 200 http://domain.com/

これでベンチを取っていると、abでLengthエラー発生!!

原因を探ってみると、nginxのログに

connect() to unix:/tmp/gunicorn.sock failed (11: Resource temporarily unavailable)

問題のこれが発生。
connectエラーですね。
最初に疑ったのがファイルオープンの上限。

$ ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 7812
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 10240
cpu time               (seconds, -t) unlimited
max user processes              (-u) 1024
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

これのopen filesですね。
ただ、よくよく考えると
そもそもsocketをコールして正常にソケットディスクリプタができているなら、これはないなということ。
今考えると少し恥ずかしいです。

次にバックログ。

#man listen
LISTEN(2)                  Linux Programmer’s Manual                 LISTEN(2)

名前
       listen - ソケット(socket)上の接続を待つ

書式
       #include <sys/types.h>          /* 「注意」参照 */
       #include <sys/socket.h>

       int listen(int sockfd, int backlog);

説明
       listen() は sockfd が参照するソケットを接続待ちソケット (passive socket) として印を
       つける。接続待ちソケットとは、 accept(2) を使って到着した接続要求を受け付けるのに使
       用されるソケットである。

       sockfd  引き数は、 SOCK_STREAM 型か SOCK_SEQPACKET 型のソケットを参照するファイルデ
       ィスクリプタである。

       backlog 引き数は、 sockfd についての保留中の接続のキューの最大長を指定する。キュ ー
       が いっぱいの状態で接続要求が到着すると、クライアントは ECONNREFUSED というエラーを
       受け取る。下位層のプロトコルが再送信をサポートしていれば、要求は無視され、これ以 降
       の接続要求の再送信が成功するかもしれない。

manコマンドです。
これのバックログが少ないのかなと。
ただ、gunicornのconfigで2048と設定しているのになぜ?という感じでした。
同じabコマンドでgunicornlocalhostで起動したベンチではこのエラーが発生しませんでした。

原因

ここまで書くと気づいている方が多数だと思いますが、

$ sysctl net.core.somaxconn
net.core.somaxconn = 128

これです。
ここがデフォルトのままでした。
かなり勉強不足を露呈しているようで恥ずかしいですが、この値でbacklogが上書きされるみたいです。
ここを変更すればエラーが出ることなくベンチとれました。
この値の変更方法は、置いといて...

とりあえずシステムコールの中身を見てみる

これを教訓にソケット周りのシステムコールを見ることに決めました。
ちなみにソースは、今更?linux kernel 2.6です。
ここからはソースをのせるだけになりますが、

実際にデフォルト値を設定しているのは、

static __net_init int sysctl_core_net_init(struct net *net)
{
    struct ctl_table *tbl;

    net->core.sysctl_somaxconn = SOMAXCONN;

    tbl = netns_core_table;
    if (net != &init_net) {
        tbl = kmemdup(tbl, sizeof(netns_core_table), GFP_KERNEL);
        if (tbl == NULL)
            goto err_dup;

        tbl[0].data = &net->core.sysctl_somaxconn;
    }

    net->core.sysctl_hdr = register_net_sysctl_table(net,
            net_core_path, tbl);
    if (net->core.sysctl_hdr == NULL)
        goto err_reg;

    return 0;

err_reg:
    if (tbl != netns_core_table)
        kfree(tbl);
err_dup:
    return -ENOMEM;
}

#socket.h
/* Maximum queue length specifiable by listen.  */
#define SOMAXCONN   128

この辺ですかね。
思いっきし128って書いてますね。
そして肝心の上書き

SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
    struct socket *sock;
    int err, fput_needed;
    int somaxconn;

    sock = sockfd_lookup_light(fd, &err, &fput_needed);
    if (sock) {
        somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
        if ((unsigned)backlog > somaxconn)
            backlog = somaxconn;

        err = security_socket_listen(sock, backlog);
        if (!err)
            err = sock->ops->listen(sock, backlog);

        fput_light(sock->file, fput_needed);
    }
    return err;
}
if ((unsigned)backlog > somaxconn)
    backlog = somaxconn;

ここで上書きされてますね。
色々勉強になりました。
もう少し掘り下げてみたいのですが、ここまでにしておきます。
ちなみに

sock->ops->listen(sock, backlog);

ここで各AF_系のlisten処理を行います。
例えば

//AF_INET ipv4
int inet_listen(struct socket *sock, int backlog);
//AF_UNIX unix domain
static int unix_listen(struct socket *sock, int backlog);

こんな感じだと思います。
この辺もっと詳しく掘り下げてみたいです。

ざっと調べただけなので間違っている箇所もあると思います。
何か気づいたら指摘してください。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした