連休の非同期祭も(疲れたので)これでラストです。
AnyEvent(5.2)でこんなコード書くと、「recursive blocking wait detected」が出ます。
use strict; use warnings; use AnyEvent; sub main { foreach my $i (1 .. 5) { my $cv = AE::cv; my $t = AE::timer $i, 0, sub { $cv->send($i); }; print $cv->recv, "\n"; } } my $t = AE::timer 0, 0, \&main; AE::cv->recv; # 結果 # EV: error in callback (ignoring): AnyEvent::CondVar: recursive blocking wait detected at XXXX.pl line 12
先にまとめ
condvarのrecvは、平たく言えばメインループ(イベントループ)です。メインループの中でさらにメインループを作ることはできません。
ただし、recvはループを回さないこともあります(対処法1と2を参照)。メインループを回すrecvの中で、ループを回さないrecvを使うことは可能です。
対処法1: cbを使う
(発表見てないですがボケてた、思いっきり聞いてましたw)YAPCでmiyagawaさんが紹介していた方法です。podによれば、
You can ensure that -recv never blocks by setting a callback and only calling ->recv from within that callback (or at a later time). This will work even when the event loop does not support blocking waits otherwise.
です。言われた通りにcondvar->cb にコールバックを渡し、その中で recv を呼べばなんとエラーになりません。
use strict; use warnings; use AnyEvent; sub main { foreach my $i (1 .. 5) { my $cv = AE::cv; my $t; $t = AE::timer $i, 0, sub { $cv->send($i); undef $t; }; $cv->cb( sub { print $cv->recv, "\n"; } ); } } my $t = AE::timer 0, 0, \&main; AE::cv->recv;
からくり
recvは、「値がsendされてくるまでイベントループを回す」という実装になっています。逆に言えば、sendが終わった後はイベントループを回さないので、イベントループの中で使うことができます。
condvarのcbはsendされたタイミングで呼ばれるので、イベントループを回しません。
対処法2: Coroを使う
podによれば、
This condition can be slightly loosened by using Coro::AnyEvent, which allows you to do a blocking ->recv from any thread that doesn't run the event loop itself.
だそうですので、アドバイスに従ってasyncしてみると動きます。
use strict; use warnings; use AnyEvent; use Coro; use Coro::AnyEvent; sub main { foreach my $i (1 .. 5) { my $cv = AE::cv; my $t; $t = AE::timer $i, 0, sub { $cv->send($i); undef $t; }; async { print $cv->recv, "\n"; }; } } my $t = AE::timer 0, 0, \&main; AE::cv->recv;
からくり
Coro::AnyEventの環境下では、recvはscheduleであって、メインループを進めません*1。また、メインループが1つ進むと、scheduleが走ります。
まずメインループのスレッドでmainが呼ばれ、ここでasyncでスレッドが5つ上がります。main関数を抜けるとscheduleが走り、5つのスレッドに処理が回ってきます。各スレッドの中ではrecvによって再びscheduleが走り、sendが呼ばれるまでメインループのスレッド(idleスレッド)へ制御が移り、メインループが進められます。
注意
本来 recursive blocking wait detected が出るようなコードに use Coro::AnyEvent をつけると、このエラーが出なくなり、原因の調査が困難になります*2。
事故を防ぐため、メインループを実行しているスレッドでは、cbの外ではrecvを呼ばないように気をつけましょう。