AnyEvent でバックエンドに EV を使う時の注意

AnyEvent を利用する際に注意する必要があることに、コールバック中で発生した例外の処理方法がバックエンドに任されている(=例外処理の方法がバックエンドによってちがう)、というのが挙げられる。

Impl::Perl では例外は単純に rethrow されるため、プログラム中で例外が発生したり die したりすると普通にプロセスは終了する。 しかし、Impl::EV の場合、デフォルトでは例外はキャッチされ標準エラーに出力されるものの、そのまま処理は続行されてしまう。

以下のような1秒タイマーをまわしてタイマーが発火したらアプリを終了する、というようなコードがあったとき、

use strict;
use warnings;

use AnyEvent;

my $cv = AnyEvent->condvar;

my $t; $t = AnyEvent->timer(
    after => 1,
    cb    => sub {
        undef $t;

        die;

        $cv->send;
    },
);

print "Backend: ", AnyEvent::detect(), "\n";

$cv->recv;

Impl::Perl な環境ではこれは期待通り動作するが、Impl::EV の場合は刺さってしまう。

例外が起きてもそのまま継続する、というのがデフォルト動作なのはどういうわけなのかよくわからないが、手元で Impl::Perl で開発していて本番で Impl::EV とかで動かすとこの違いによってはまることはかなりありそう。

この挙動を変えるためには EV の例外処理を上書きするようにすれば良い。 AnyEvent では post_detect というものでバックエンドモジュールが決まったときのフックを差し込める。それをつかって、

AnyEvent::post_detect {
    if ($AnyEvent::MODEL eq 'AnyEvent::Impl::EV') {
        no warnings 'once';
        $EV::DIED = sub { $cv->croak($@) };
    }
};

このようなコードを入れておけばまぁいいんだとおもう。EV のドキュメントにも書かれているが、$EV::DIED で例外処理を上書きすることはできるのだが、この関数内で発生した例外は無視するそうなので(これどうなのw)、例外を rethrow するというようなことはできない。なのでこのようにどっかに condvar を置いておいてそれの croak を呼ぶというようなことをする必要がある。

See also:

by typester / at 2012-05-24T09:01:00 / perl · anyevent / Comments(0)