Tomcat のログを見ると "Too many open files" のエラーが出力されてるとします。ログの内容通り、これは Tomcat プロセスが、1 プロセスが開けるファイルディスクリプタの上限を越えてもまだファイルディスクリプタを開こうとしているわけです。そもそもファイルディスクリプタ数を調整していないのは論外として、このケースで疑わなければならないのは、ファイルに代表されるリソースの閉じ忘れです。
Unix システムであれば、特定のプロセスが開いているファイルっていうのは一覧化することができて、lsof コマンドで取得できます。
$ lsof -p PID java 1456 tomcat 23r REG 253,1 27699 104136845 /data/tomcat6/lib/el-api.jar java 1456 tomcat 24r REG 253,1 742089 104136851 /data/tomcat6/lib/tomcat-coyote.jar java 1456 tomcat 26r REG 253,1 228175 104136843 /data/tomcat6/lib/catalina-tribes.jar java 1456 tomcat 27r REG 253,1 1128229 104136844 /data/tomcat6/lib/catalina.jar (略)
リークしているときは、ここに異常な数のファイルだったりがある場合がありますが、
java 1456 tomcat 32u sock 0,5 219309526 can't identify protocol
こんなのが大量、数千単位で出力されている場合もあるんですね。これ、"sock" って出ているところから分かるとおり、ファイルじゃなくてソケットです。Tomcat はソケットを開いたままにしてしまって、"can't identify protocol" と出ている通り、もはや何のプロトコルかの情報すら OS から消えてしまったっていう場合ですね。
で、このケースで何を疑うべきか、というところなんですが、まぁもちろん、ソケットの閉じ忘れってヤツが疑われます。ただ、何のプロトコルか分からないわけなので、基本的な方針としては、Tomcat のポートに関連する TCP コネクションの情報を定期的に監視するっていうのが王道です。要するにサーバ側で netstat を取りましょうってヤツですね。 で、もしも CLOSE_WAIT が大量になっていることまで突き止められたのであれば、Apache の httpclient の使い方を疑いましょう。
このあたり、知の宝庫インターネット上に色々と情報はまとまっているんですが、検証込みで一番分かりやすかったのはこちらです。
チュートリアルの中に
// Release the connection. method.releaseConnection();
みたいなことが書いてあって、いかにもコネクションをクローズしてくれそうな雰囲気がありますが、これは意味が違います。HttpClientが持つ、HttpConnectionManagerのインスタンスにコネク> ションを返却しますよ、という意味であって、別にコネクションを閉じてくれるわけではないんですよね。
原因としてはこれが全てで、対策としても上記エントリに記載のあるとおり、
- HttpConnectionManagerの持つ、アイドル状態のコネクションをクローズする(これにはいくつかの方法があります)
- HTTPヘッダに「Connection: close」を追加する
ってことになります。
で、これだけだと少々淡白なので、なぜ「CLOSE_WAIT」でコネクションが溜まってしまうのかっていう話なのですが、httpclient でコネクションプールを使ってる前提ですと、上記のエントリに記載のあるとおり、releaseConnection だけじゃコネクションは切断してくれず、コネクションをプールに返却するだけと。 すると HTTP サーバ側で TCP コネクションを切断しようとするんだけど、まぁご存知の通り、TCP コネクションの切断というのはコネクションの両端が切断に同意しないとなかなか切ることができません。同じ障害を扱うエントリとしては以下の記載がわかりやすいです。
First, a quick TCP refresher… When a TCP connection is about to close, its finalization is negotiated by both parties. Think of it as breaking a contract in a civilized manner. Both parties sign the paper and it’s all good. In geek talk, this is done via the FIN/ACK messages. Party A sends a FIN message to indicate it wants to close the socket. Party B sends an ACK saying it received the message and is considering the demand. Party B then cleans up and sends a FIN to Party A. Party A responds with the ACK and everyone walks away:
A --FIN-> B A <-ACK-- B A <-FIN-- B A --ACK-> B
The problem comes in when B doesn’t send its FIN. A is kinda stuck waiting for it. It has initiated its finalization sequence and is waiting for the other party to do the same.
そういうわけで、クライアント側が「切断」しようとしないので、CLOSE_WAIT で残るってわけですね。はい。