TheSchwartz の worker を安全に停止する

TheSchwartz の worker を Ctrl-C とか kill で止めた場合に、job の処理が半端な状態で終わられると困る、という話。以前 Deamon::Generic で TheSchwartz の worker をデーモン化する(2) - 酒日記 はてな支店 で諦めたんだけど、ちょっと必要に迫られたので考えてみたら一応できたっぽい。

package MyWorker;
use strict;
use warnings;
use base qw( TheSchwartz::Worker );

sub sighandler {
    warn "caught signal @_\n";
    no warnings 'redefine';
    *TheSchwartz::work_once = sub { exit };
}

sub work {
    my ($class, $job) = @_;
    $SIG{HUP} = $SIG{INT} = $SIG{TERM} = \&sighandler;
    $job->completed();
}

シグナルハンドラで TheSchwartz::work_once を exit するだけのサブルーチンで再定義してしまう。けっこう無理矢理だ。

なぜこれでうまく行くかというと、TheSchwartz::work は以下のようになっているので

sub work {
    my TheSchwartz $client = shift;
    my($delay) = @_;
    $delay ||= 5;
    while (1) {
        sleep $delay unless $client->work_once;
    }
}

無限ループ内で延々 work_once が呼ばれる形 (処理すべき job がなければ work_once は偽を返すので、一定時間 sleep)。ということで、work_once を exit するように書き換えてやれば、そこでプロセス終了。
最初はシグナルハンドラでフラグをセットして、work の最後でフラグが立っていたら exit、みたいに書いたのだが、work を抜けてから job の後始末があるのでよろしくない。 なのでこんな風にしてみた。
「必要に迫られて」というのは、worker をずっと動かしているとだんだんメモリ消費量が増えていく、という現象が起きているから。もちろんそれはそれで原因を解明しないといけないのだけど、とりあえずは定期的に再起動して凌ごうかと。