ebnetd/ebhttpd がゾンビ作って止まってしまう件

辞書/辞典サービスなんてネット上にあまたあるからいいやとも思うこともありますが,ローカル「サーバ」にあったらあったで便利です*1。そんなときに使えるのが笠原さん@SRA の作ったebnetd に付属の ebhttpd です*2

昔から FreeBSD 上で愛用していたんですが,Fedora 上でいざ動かしてみると,しばらく使っているとサーバの応答が止まってしまいます。ps で見ると defunct なプロセスがたくさん。inetd モードで動かすと問題なく動くのですが,レスポンスを考えると standalone モードで動かしたい。

色々情報を探してみたんですけれど,特に問題のあるコードには見えずサッパリでした。で,今日いろいろいじっていてなぜかうまく動くコードができました。

accept したあとのプロセスが終了した時にうまく SIGCHLD がとれないみたいだったので,ebnetd/ebnetd.c *3の wait_child() という関数を,

static RETSIGTYPE
wait_child(signal_number)
    int signal_number;
{
    int status;
    pid_t child_pid;

    do {
        child_pid = waitpid(-1, &status, WNOHANG);
    } while (child_pid > 0);
}

みたいなシンプルな形にしてみたらとりあえず止まらなくなりました(まだハードなテストはしていないのでなんともいえないですけれど)。

このコード,IPAのセキュアプログラミング講座のhttp://www.ipa.go.jp/security/awareness/vendor/programming/b07_04.html からパクっただけのものです。で,元のコードと何が違うのかというと,

  • どうせ SA_RESTART してるので set_signal_handler(SIGCHLD, wait_child) をはがした
  • syslog(LOG_INFO, "receives SIGCHLD") をとった

くらいの違いしかありません*4。前者だけじゃだめだったんで,ミソは後者,おそらく syslog() がリエントラントじゃなくて,シグナルハンドラで呼んでいたのがブロックしちゃっていたんではないのかなぁ〜という予想です。

*1:もちろん富豪的にはクライアントPCにインストールされているともっと便利ですけど

*2:残念ながら長らくメンテされてないです

*3:この ebnetd/ebnetd.c は ebhttpd/ebhttpd.c にビルド時コピーされるのでこちらだけいじればよいのです

*4:細かいところではループが while → do〜while になってますが