🚥

Laravelのバッチとかキューワーカーでシグナルハンドリングする

2021/09/29に公開

はじめに

Laravelのバッチやキューワーカーでシグナルハンドリングする方法を記載したものです。

環境

PHP7.1以上
Laravel5.5以上

なぜやるのか

ソフトウェアデリバリーの安全性を高めるため、実行プロセスの後始末を確実に行うため。

Laravelのシグナルハンドリングってどうなってるのか

Laravelでシグナルハンドリングをしているのはartisan queue:workで実行するデーモンプロセスのみ。
Laravel自身がハンドリングしているのはSIGTERM。

どういった場合にシグナルが送られてくるのか

  • supervisorctl stop / shutdownした場合
  • ジョブの実行が長すぎてタイムアウトした場合

他にも何かあるかもしれないけど上記以外は知らないので他にあれば教えてください。

supervisorctl stop / shutdownした場合

supervisorctl stop / shutdownした場合は、SupervisorのプログラムがSIGTERMを送ってきます。
通常LaravelはSIGTERMを受けると、実行中のワーカージョブが完了するのを待って正常終了します。
ただし、以下の場合においては問題が発生します。

  1. 実行環境がPOSIX準拠の環境(Linux / Unix)で動いていて
  2. ワーカージョブから更に別のartisanコマンドを外部プロセスとして実行している

POSIX準拠の環境の場合、killにてシグナルが送信されることになります。
この場合、子プロセスにもシグナルが伝搬するため、シグナルハンドリングをしていないと、突然killされます。
上述のような環境でプログラムを実行している場合、ソフトをリリースする度に障害を実は起こしている可能性があります。

対処方法

SIGTERMを以下のような感じで拾うようにしときます。

pcntl_async_signals(true);
pcntl_signal(SIGTERM, static function () use ($command) {
    // ハンドリングしたときに実行させたい処理
    // (あまりないと思うけど)直ちに終わらせたい場合は例外投げると止められれます
});

バッチ処理をとりあえず走り切らせたい場合は、クラスを1個作って上記処理を記述しておき、
App\Console\Kernelの$bootstrappersに登録しておくのも有効だったりします。
この場合、kill プロセスIDでは終了を待つようになるため、一刻も早くkillしたい場合は、kill -9 プロセスIDを叩くことになります。

ちなみにSupervisorが終了命令出してから、設定ファイルのstopwaitsecsに書いた秒数経過しても実行中の場合は、kill -9で殺しに来ます。
普通に動けば終わるであろう十分な猶予を設定ファイルに書いておくと良いです。

ジョブの実行が長すぎてタイムアウトした場合

タイムアウトした場合は、SIGALRMを"Laravel"が投げつけてきます。
ジョブの実行状態をDBなどに持って排他しているような場面では、シグナルハンドリングをしていないと実行中のまま残ったりしてかっこ悪いです。

対処方法

pcntl_async_signals(true);
pcntl_signal(SIGALRM, static function () use ($command) {
    // ハンドリングしたときに実行させたい処理
    // 終わる見込みがあるならもう1回やらせても良いし、
    // 見込みがないなら、タイアウトのステータスを書いて終わらせます
    // 例外投げないと最後まで走り切ろうとするので、例外は投げましょう
});

その他

シグナルハンドリングはWEBアクセス側でやろうとすると、
Laravelはpcntl_signalを無効化してるのでエラーになります。
ので、QUEUE_DRIVER=syncが使えなくなるので、開発時はお気をつけ下さい。

終わりに

Laravelの上で動かすアプリでシグナルハンドリングをしたい!をお届けしました。

Discussion