もじもじ <mojimoji...@yahoo.co.jp> wrote
  in <20151204180446.a504.60e52...@yahoo.co.jp>:

mo> 仮にそうだとしても、そもそも CLOSED が表示されるのはなぜ
mo> なんでしょうか。

 CLOSED はそもそも表示される可能性がまったくないものでは
 ありませんが、FreeBSD 7 以降は、それ以前より表示される可能性が
 高くなっています。これは、TCP 通信時に使われる構造体の
 メモリ領域の確保の方法が、4.4BSD で使われていたものから
 変更されたからです。

 socket API で通信を行なう場合、ファイル記述子、ソケット、
 IP プロトコル制御ブロック、TCP プロトコル制御ブロック、のように、
 通信の各層で使われる構造体がカーネルに順番に確保されます。

 4.4BSD の場合、それぞれの構造体は必要がなくなった時点で解放されます。
 たとえば、アプリケーションが通信終了時にファイル記述子を
 close した時、ソケットやプロトコル制御ブロックの構造体は、
 すぐには解放されません。FIN の交換など、プロトコルで要求される
 終了シーケンスが終って、初めて解放されます。
 逆に、先に通信相手から FIN が送られてきて TCP 接続が
 切断された場合、FIN 交換が終った時点でTCP プロトコル制御ブロックの
 領域が、先に解放されます。

 FreeBSD 7 には、この細分化されている構造体の確保・解放を、
 ある程度まとめる変更が入りました。
 7.0 以降では、たとえば、TCP 通信が FIN を交換して終了シーケンスを
 完了していたとしても、ファイル記述子を close しないと
 TCP プロトコル制御ブロックが解放されません。
 この状態の時、netstat の結果に CLOSED として現れます。
 「CLOSED が見えることがほとんどない」とよく説明されているのは、
 前述したとおり、4.4BSD ではファイル記述子が close されてないとしても、
 FIN を交換した時点で TCP プロトコル制御ブロックが
 解放されるようになっているからです。

 この変更の動機は、FreeBSD が SMP に対応してカーネル各部が
 並列処理されるようになったことにより、相互に関連する構造体の
 参照・確保・解放にかかるコストが大きくなったためです。
 構造体同士はポインタで参照されているので、確保・解放には排他制御が必要です。
 そのような相互参照があると、構造体へのアクセスのたびにロックが発生して
 性能が低下してしまいますし、領域が本当に確保されているのかどうかを
 確認したり、確保できなかった時のエラー処理などがかなり複雑になります。

 そのため、頻繁に使われる組み合わせについては、不要になってもすぐに
 解放しないようにしました。
 これにより多くの排他制御を省くことができるようになりましたが、
 その一方で、メモリの使用量が少し増えることと、構造体解放までの
 時間が長くなるという副作用が観察されるようになっています。

 ただし、実用上、TCP 接続が切断された後に、ソケットを close しないで
 放置するという状況は考えにくいので、まともに書かれているプログラムなら、
 やっぱり CLOSED はすぐに消えます。

 7.0 が出た直後くらいから、「CLOSED が出て消えない」というレポートが
 いくつかあがっていました。調査の結果、ほとんどがアプリケーション側の
 バグが顕在化したことによるものでした。
 users-jp 95621 にはログなどの症状に関する生情報がないので
 何とも判断できませんが、Courier IMAP 同じ問題を抱えているのかも知れません。

 CLOSED が出ている状態の netstat と、sockstat の出力を
 並べて比較してみてください。1 個のCLOSED 状態の接続について、
 対応する sockstat の行の FD のカラムにファイル記述子の数値が
 出ていて、かつ CLOSED を発生させているアプリケーションを
 終了させた時に、すぐに CLOSED が全部消えるのであれば、
 TCP 接続断後の close() に失敗していて、
 記述子がリークしている可能性が高いのではないかと思います。
 FreeBSD 7 以降では、socket をオープンした時の
 記述子を正しく close しないと、切断後の TCP 接続が
 CLOSED のまま残るからです。

 過去にあった典型的な例は、close() の引数に閉じるべきでない記述子が
 指定されていて少しずつリークしていたものの、リークによる
 メモリ使用量が小さく、記述子数の上限に引っかかることもなかったので
 長い間気づかれていなかった、というものでした。
 close() の戻り値をチェックしないプログラムが多いので、この手の
 バグは気づかれにくいようです。

-- Hiroki

Attachment: pgpeFtFFFlo7R.pgp
Description: PGP signature

メールによる返信