Blog

ApacheにSIGWINCHを送って停止しよう!

みなさんこんにちは。最近コストコでSinghaビールをたくさん買った山口です。
Singhaは氷を入れて飲むとおいしいと思います。

さて、みなさんApacheは好きですか?私はNginxの方が好きです。
今回はApacheのgraceful stop(緩やかな停止)に関する雑談です。

動機

Apacheのドキュメントを読んでいると、「緩やかな停止」のシグナルにWINCHが割り当てられている記載がありました。
https://httpd.apache.org/docs/current/stopping.html#gracefulstop

シグナルWINCH(SIGWINCH)は本来、端末サイズの変更(Window Change)を表すシグナルです。しかしApacheでは、そのシグナルをgraceful stopに割り当てています。

「端末のリサイズ用シグナルでApacheを止める」というのは直感に反するので、なぜこうなっているのか気になりました。

Apacheの停止・再起動シグナル一覧

まず整理のため、UnixのApacheが使う主なシグナルをまとめます。

操作 シグナル コマンドの例
停止 TERM apachectl -k stop
再起動 HUP apachectl -k restart
緩やかな再起動 USR1 apachectl -k graceful
緩やかな停止 WINCH apachectl -k graceful-stop

graceful restart(USR1)とgraceful stop(WINCH)は似ていますが、後者は親プロセス自身も最終的に終了します。

なんでSIGWINCHなの?

公式ドキュメントでは、SIGWINCHを採用している理由は明記されていません。

Stack Overflowでは「Unixのシグナルが不足しているため、仕方なくSIGWINCHを使っているのだろう」という説明がありました。
https://stackoverflow.com/questions/780853/what-is-in-apache-2-a-caught-sigwinch-error

But unfortunately apache2 process poorly misuses this signal (in the way they divert its first meaning), but for their defense, they seems not to have a choice and had to resort to this due to a lack of signal (see bug report). One of their assumption is that apache2 process is always in background.

Unixでは、TERM, HUP, USR1, QUIT あたりが制御によく使われると思います。Apache 2.xではUSR1がgraceful restartに固定され、graceful stop用のシグナルを別途確保する必要がありました。WINCHは「端末に紐づいたフォアグラウンドプロセス」向けのシグナルで、デーモンとしてバックグラウンドで動くApacheには通常届きません。そのため、WINCHが割り当て先として選ばれた、というのが実情のようです。

ソースの話

graceful stopは include/mpm_common.h で次のように定義されています。
https://github.com/apache/httpd/blob/trunk/include/mpm_common.h#L85

#define AP_SIG_GRACEFUL_STOP SIGWINCH

一方、graceful restartはUSR1です。

#define AP_SIG_GRACEFUL SIGUSR1

gitの履歴

gitのログを辿ると、SIGWINCHとApacheの関係は段階的に変わっています。

  1. 2001年: Prefork MPMがgraceful restartにSIGUSR1の代わりにSIGWINCHを使っていた
    https://github.com/apache/httpd/commit/da4502c070611095e30f6a5f0aa7bfd7e817867b
  2. 2005年: graceful restartのシグナルをUSR1に固定し、SIGWINCHをgraceful stop用に空けた
    https://github.com/apache/httpd/commit/395896ae8d19bbea10f82b1d40e16f4721d316b7
  3. 2005年: `AP_SIG_GRACEFUL_STOP` のマクロ定義が追加された
    https://github.com/apache/httpd/commit/bbb8aeee0237265af89587728a9bde0be7a1e96a

上記の2001年のコミットは「graceful stopがUSR1からWINCHに変わった」というより、「当時はgraceful restartにWINCHを使っていた」ようです。
現在の役割分担(USR1 = restart、WINCH = stop)は2005年頃に整理されました。

SIGUSR2を使うビルドオプション

Apacheのビルド時に --enable-sigusr2 オプションを指定すると、graceful stopにSIGUSR2を使えます。
https://github.com/apache/httpd/blob/trunk/configure.in#L844

$ cd apache/httpd # httpdのディレクトリへ移動
$ configure --enable-sigusr2

configure.in ではデフォルトがWINCH、オプション指定時にUSR2に切り替わるようになっています。

Apacheを自分でビルドしてインストールすることは滅多にないと思うので忘れて良いと思います。

graceful stopの内容

graceful stopを送ると、親プロセスは子プロセスに「処理中のリクエストを終えたら終了しろ」と伝え、自身はPidFileを削除してポートのlistenを止めます。子プロセスがすべて終了するか、GracefulShutdownTimeout で指定した時間が経過するまで親プロセスは待ち続け、その後自身も終了します。

httpd -X などでApacheを端末上で動かしていると、ウィンドウをリサイズしただけで graceful stop が実行されます。デバッグ中に不意に止まる原因になり得ます(このあと実際にやってみます)。

ApacheにSIGWINCHしてgraceful stopしてみよう!

今回はApacheをフォアグラウンドで実行、ウィンドウサイズを変更し、graceful stopをしてみます。
-X オプションでフォアグラウンド実行すれば、端末に紐づいた状態になるので、ウィンドウをリサイズするだけで SIGWINCH が届きます。

手順

今回はDockerの公式イメージを利用します。
-X はデバッグ用のシングルプロセスモードで、端末を占有したまま httpd を実行できます。

# Apacheを実行する端末
$ docker run -it --rm --name httpd-test -p 8080:80 httpd:2.4 httpd-foreground

httpd が起動したら、httpd を動かしている端末のサイズを変更します。カーネルが SIGWINCH を送り、Apache はそれを graceful stop として処理します。ログに次のようなメッセージが出れば成功です。

[mpm_event:notice] [pid 1:tid 1] AH00492: caught SIGWINCH, shutting down gracefully

参考: 運用でgraceful stopする場合

多くの環境ではApacheをデーモンで実行しているので、apachectlやsystemctlといったマネージャを使ってgraceful stopを実行します。

# 内部で kill -WINCH を親プロセスに送信している
$ sudo apachectl -k graceful-stop

PidFile の親プロセスに直接WINCHを送り、graceful stopすることもできます。
普通にapachectlやsystemctlを使ってください。

$ kill -WINCH "$(cat /path/to/httpd.pid)"

まとめ

Apacheのgraceful stopがSIGWINCHなのは、Unixのシグナル割り当ての歴史的経緯に起因するようです。
端末リサイズ用のシグナル名を使うのは紛らわしいですが、デーモンとして動くApacheには通常届かないため、実運用上の問題はないと考えられます。