なんかふと気づいたら最近以前書いたPerlでシグナル処理の記事にブクマがついていたので続き的な感じで書いてみた。

例えば 以下のように、ワーカーとかでずーーーーっとDBにクエリを投げてその結果を使って処理をする、というような処理を書くとする

     while ( $loop ) {
         my $sth = $dbh->prepare( .... );
         $sth->execute();
         while ( $sth->fetchrow_arrayref ) {
              ....
         }
     }

以前書いた%SIGを用いたPerlの普通のシグナル処理では、もしexecute()でブロックしていた場合など(例:Q4Mでqueue_waitしてる)ではいくらSIGINTとかを送ってもブロックしたまんまになる。理由はPerlの素のシグナル処理は*Perlの*1オペと1オペの間に実行されるから。データベースに対する処理はCレベルでブロックしていて、実はこれはほとんどの場合Perlの1オペがブロックしてる間に終わらない

そういう場合は%SIGでシグナル処理をするのではなくて、POSIX::SigActionとかを使う。POSIXレイヤーでシグナルハンドラを指定できるので、Cレイヤでのブロック状態中でもシグナルハンドラを呼び出せる。Perlの世界から見ると、Perlのオペ完了を待たずにすぐに実行されるのでこれらをリアルタイムシグナルと呼ぶ。基本の使い方はperldoc POSIXした時にでてくる。

で、シグナルを受け取った時に現在実行中のステートメントハンドラをキャンセルするには$sth->>cancelを呼べばいいし、ついでにDBも接続を落としちゃうなら$dbh->disconnectとかしちゃえばいい

    use POSIX qw(:signal_h); # import SIGINT, SIGTERM, and other signal related stuff

    my $sth; # declare now to make it accessible
    my $sigset = POSIX::SigSet->new( SIGINT ); # specify which sig we're handling
    my $cancel = POSIX::SigAction->new(sub { # the handler
        if ( $loop ) {
            eval { $sth->cancel };
            eval { $dbh->disconnect };
            $loop = 0;
        }
    }, $sigset, &POSIX::SA_NOCLDSTOP);
    POSIX::sigaction( SIGINT, $cancel ); # register them handler

    while( $loop ) {
        $sth = $dbh->prepare( ... );
        $sth->execute();
        while ( $sth->fetchrow_arrayref ) {
             ....
        }
     }

これでSIGINTでちゃんとキャンセルされる。ただ、当然ながらキャンセルされた結果undefになる変数とかがあるなら、それは while の中で参照しないように自分で気をつけて処理をするように。

あと、sigaction() でシグナル設定するの面倒くさいよ!って人は%SIGRTっていう、リアルタイムシグナル用の%SIGハッシュがあるらしい。自分は間違えそうなので使ってないけど。use POSIX; すると自動的に現スコープから参照できるようになる。

そうそう、激しくオフトピだけど、use POSIX; は、実に数百個(うろ覚え)もの関数や変数を現パッケージにインポートするので、効率を重視したいなら注意。必要なものだけ明示的にインポートするか、常にPOSIX::hogeのようにして参照するほうがよいだしょう。