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:
AnyEvent 5.3 Released
出てました。気がつきませんでした。
このバージョンから AnyEvent::Impl::Cocoa が入りました。これは Cocoa::EventLoop を AnyEvent から使うアダプターで、
use Cocoa::EventLoop;
していると自動的に使われます。したがって、
use AnyEvent;
use Cocoa::EventLoop;
# AnyEventを使用したコード...
と言うように書くと自動的に Cocoa のイベントループで AnyEvent が動作するというわけです。 こうしておけば Cocoa::Growl など、Cocoa::EventLoop を必要とするモジュールをシームレスに AnyEvent 内で使うことが出来て便利です。
なお、AnyEvent::Impl::NSRunLoop は DEPRECATED ってことでとりあえずドキュメントに注意書きを加え、さらに数週間後にはCPANから削除するつもりです。
AnyEvent::Impl::NSRunLoop
っていう頭おかしいモジュールを作ってるんですが、これについていくつか schmorp (AnyEvent作者)とはなして以下のようにしていくことにした。
- NSRunLoop の実装は Cocoa::RunLoop と言うモジュールに外だし
- AE::Impl の方はそれをただ使うだけ
- Cocoa::RunLoop は AnyEvent に依存しなくても使える
- AnyEvent は Cocoa::RunLoop がロードされている環境ではバックエンドに Impl::NSRunLoop をつかう
と言うわけで将来的には、Cocoa::Growl とかそれ系のモジュールはシームレスに AnyEvent 内で使えるようになる感じです。
A simple chat server in AnyEvent
Node.js でつくってるやつ をみて同じくらいで書けそうだなと思ったので試しに AnyEvent で書き直してみた。
#!/usr/bin/perl
use strict;
use warnings;
use AnyEvent::Socket;
use AnyEvent::Handle;
my @clients;
tcp_server undef, 7000, sub {
my ($fh) = @_ or die $!;
my $h = AnyEvent::Handle->new( fh => $fh );
my $leave = sub {
my $client = delete $clients[ fileno($fh) ];
for my $c (grep { defined } @clients) {
$c->{handle}->push_write("$client->{name} has left.\n");
}
};
$h->on_read(sub {
shift->push_read( line => sub {
my ($h, $line) = @_;
my $client = $clients[ fileno($fh) ];
unless (defined $client->{name}) {
if ($line =~ /(\S+)/) {
$client->{name} = $1;
$h->push_write("===========\n");
for my $client (grep { defined } @clients) {
next if $client->{handle} eq $h;
$client->{handle}
->push_write( "$client->{name} has joined.\n" );
}
}
return;
}
my ($command) = $line =~ m!^/(.+)!;
if ($command) {
if ($command eq 'users') {
$h->push_write("- $_->{name}\n") for grep { defined } @clients;
}
elsif ($command eq 'quit') {
$leave->();
}
return;
}
for my $c (grep { defined } @clients) {
next if $c->{handle} eq $h;
$c->{handle}->push_write( "$client->{name}: $line\n" );
}
});
});
$h->on_error(sub {
my ($h, $fatal, $msg) = @_;
$leave->();
$h->destroy;
});
$clients[ fileno($fh) ] = {
name => undef,
handle => $h,
};
$h->push_write("Welcome, enter your username:\n");
};
AE::cv->wait;
行数的にはほぼ同じくらい。
追記@2009-12-08T10:36:37+09:00: left なメッセージがおかしかったので直した。