net.SocketでListener数が多すぎと怒られる

818 views
Skip to first unread message

MATSUNAGA Ichiro

unread,
May 24, 2012, 5:20:56 AM5/24/12
to node...@googlegroups.com
松永です。

nodeに興味が出て、勉強のprogramを書いてるところです。
javascript自体、ecma scriptの仕様書を見ながら書いているレベルなので、node自体の使い方の質問では無く、javascriptの質問になっているかもしれませんが、その際は指摘していただければと思います。


書いているのは、httpへの負荷をかけるツールです。
書いたcodeを質問用に簡略化したのが、
https://github.com/vikke/node.testprograms/blob/961e54c3fbde0d53b9daafdd80a694d07539f896/http-request.js
です。
簡略化前が
https://github.com/vikke/node.testprograms/blob/2ee4953377838ced85c58ce715fb957bf1490906/http-request.js
です。

これを走らせると
--------------------------------------------------
(node) warning: possible EventEmitter memory leak detected. 21 listeners added. Use emitter.setMaxListeners() to increase limit.
Trace
at Socket.<anonymous> (events.js:167:15)
at Socket.once (events.js:188:8)
at ClientRequest.<anonymous> (/home/vikke/vcswork/node.testprograms/http-request.js:52:13)
at ClientRequest.g (events.js:184:14)
at ClientRequest.emit (events.js:114:20)
at http.js:1432:9
at EventEmitter._tickCallback (node.js:245:11)
--------------------------------------------------
のようなwarningが出ます。

疑問なのが
1.
51行目で、走ると思われる並列数分setMaxListeners()しているのに、それ以上のlistenerを追加しようとしているのはなぜ?

2.
そもそも、http接続毎に生成されるhttp.ClientRequest用のsocketのconnect eventを、once()でつかまえようとしているのに、複数回のlistener登録が行なわれるのはなぜ?

3.
結局どう書くのがnode流なの?


使っているnodeはubuntu 12.04上にnvmで導入した0.7.8です。

以上、よろしくお願いします。

--
MATSUNAGA Ichiro
e-mail: vikk...@gmail.com
who am i: https://plus.google.com/103000727838025680940/about

GPG fingerprint = DCEF C86E 2930 45D0 0941 E977 4DCE A95F 3914 4BED

Shigeki Ohtsu

unread,
May 24, 2012, 9:28:26 AM5/24/12
to node...@googlegroups.com
大津です。

これは http.Agent に関する非常に良い課題だと思います。

# 本当は XXXX の YYYY を見てくださいと言いたいところなんですけど、それは
今後の楽しみにしていただけるとありがたいです。

> nodeに興味が出て、勉強のprogramを書いてるところです。
> javascript自体、ecma scriptの仕様書を見ながら書いているレベルなので、node自体の使い方の質問では無く、javascriptの質問になっているかもしれませんが、その際は指摘していただければと思います。

いえJSの問題ではなく Node.js 固有の原因によるものですので安心してください。

> 書いているのは、httpへの負荷をかけるツールです。
> 書いたcodeを質問用に簡略化したのが、
(中略)
> のようなwarningが出ます。

まず Node.js のHTTPクライアントの動作について少し解説が必要かなと思います。

Node では、通常 http.request() で生成したリクエストオブジェクト(req)が
HTTPサーバに接続する際、リクエストオブジェクトが直接 HTTPサーバにソケッ
ト接続するのではなく、http.Agent が替りにソケット接続を行い、ソケットと
リクエストを結びつける管理する役割を担います。

ソケットの最大同時接続数は http.Agent.maxSockets の値が上限で、それを超
えるとリクエストは queue に溜まります。
Agent は接続したソケットをHTTPリクエストに割り当て、HTTPリクエストが終了
すると Agent は既存のソケットを使いまわして他のリクエストに再度割り当て
ます。(HTTP keep-alive が有効になっていることが前提です。)

リクエストの際、特に Agent の指定をしなればデフォルトで globalAgent が利
用され、最大同時接続数は5つに制限されています。(管理単位は
host+port+sourceIP)

上記の知識を前提として以下に回答を書いてみます。

> 疑問なのが
> 1.
> 51行目で、走ると思われる並列数分setMaxListeners()しているのに、それ以上のlistenerを追加しようとしているのはなぜ?

'socket' イベントで渡される socket オブジェクトはデフォルト(globalAgent
利用時)では5つです。毎回 golbalAgent がリクエストに socket を割り当てる
度にその5つに対してリスナ追加をするので setMaxListners() 値を超えてし
まっています。

> 2.
> そもそも、http接続毎に生成されるhttp.ClientRequest用のsocketのconnect eventを、once()でつかまえようとしているのに、複数回のlistener登録が行なわれるのはなぜ?
>

globalAgent 利用時 socket は 'connect' イベントは最初に接続した5回分しか
emit しません。(途中で接続が他の理由で切れない前提ですけど)
なのでいくら once でリスナ登録しても emit されないのでリスナは次々登録さ
れる状況になります。

> 3.
> 結局どう書くのがnode流なの?

うーん、いろいろやり方があります。100個ぐらいなら一気に http.request()
してやればメモリは多少使いますが、 Agent が勝手に接続をしてくれるでしょう。

スケールするよう最大接続数を parallel のカスタムエージェントを作って、レ
スポンス終了毎に順次リクエスト追加するやり方場合を下記 gist に置きました。

https://gist.github.com/2781370

コメントとか書いていませんが、今後の参考にしていただけたらと思います。
(gjslint ベースに書式は変えてます。)

MATSUNAGA Ichiro

unread,
May 25, 2012, 2:49:57 AM5/25/12
to node...@googlegroups.com
松永です。

On Thu, May 24, 2012 at 10:28:26PM +0900, Shigeki Ohtsu wrote:
> 大津です。
>
> これは http.Agent に関する非常に良い課題だと思います。
----------- snip -------------------------------

非常に丁寧な解説、ありがとう御座います。

http.Agentの件、ちゃんとマニュアルに書いてありますね。
とりあえず、httpのマニュアルをちゃんと通読してみます。

理解出来ているかを確認する為、自分の言葉で表現してみます。
間違いがあるようでしたら、指摘していただけるとありがたいです。

・Agentによるsocket poolingをしているよ
・同一host且つ同一portへのqueryの場合は、defaultはkeep aliveで
つなぎに行ってるんで、すでに繋がっているsocketをかえすよ。
・だから、実はhttp query毎にsocketを生成しているんじゃないから
connect listenerの登録を、実は同一socketに複数回行なっているよ。
・しかも、socketのconnect自体は一回しかしないから、それ以降に登録
されたlistenerへはconnectは発火されないんで、onceで登録しても
溢れるよ。

とりあえず、wiresharkで確認してみると、keep aliveを使って繋ぎ、
apacheの設定通り100回のrequestまでは同一connectionを使ってました。

もう少し調べて、keep aliveを使わない接続方法も出来るようにしてみます。
(ab cloneをとりあえず目指してみます)

あと、sample code、非常に勉強になりました。
基本はthreadで作るのと同じように、並列数分処理の流れを作り、処理の最後で
再帰的に次の処理を呼ぶ形になるようですね。
で、Agentのsocket pooling数を並列数に設定しておく。

以上、ありがとう御座いました。

# gjslintなんてものがあるんですね。便利!

Shigeki Ohtsu

unread,
May 25, 2012, 5:09:07 AM5/25/12
to node...@googlegroups.com
大津です。

> 理解出来ているかを確認する為、自分の言葉で表現してみます。
> 間違いがあるようでしたら、指摘していただけるとありがたいです。

はい、大丈夫です。

> もう少し調べて、keep aliveを使わない接続方法も出来るようにしてみます。

頑張ってください。調べると言っている人に答えを教えちゃうのはどうよ、
とまたつっこまれちゃいますので今回は解答を書かないことにしますw

> あと、sample code、非常に勉強になりました。
> 基本はthreadで作るのと同じように、並列数分処理の流れを作り、処理の最後で
> 再帰的に次の処理を呼ぶ形になるようですね。
> で、Agentのsocket pooling数を並列数に設定しておく。

これちょと悩んだんですが、maxSockets 数よりちょっと多めにリクエストを起
動しておいて、いつも少し queue に溜めた状態にしておくとより速くなるん
じゃないかなと思ってます。

あと接続が切れたときのリトライ処理やつなぎっぱのタイムアウト処理なんかの
エラー系手当も必要ですが全然考慮してません。
この辺 Node でシステム監視するようなモジュールの中身を見ると良いロジック
があるかもしれません。

> # gjslintなんてものがあるんですね。便利!

Node のJS系ソースコードはこれをベースとした lint をかけているので、慣れ
ておくと今後PR出す時に役に立ちますよ。ちなみにカンマファーストは対応して
ないようです。
Reply all
Reply to author
Forward
0 new messages