理系学生日記

おまえはいつまで学生気分なのか

AnyEvent で外部コマンドを実行するときの注意点

AnyEvent::Util に run_cmd ってのがあるので、これを使うと良いのではないかと思う。
run_cmd は AnyEvent の Condition Variable ($cv) を返す。外部コマンドが実行を終えると、run_cmd 内部ではこの $cv に対して send が呼ばれる*1。外部コマンド実行後のタイミングで実行されるコールバックは $cv->cb で設定する。

my $cv = run_cmd [qw(sleep 5)];
print "now sleeping ....\n";

$cv->cb( sub { print "wake up\n"; } );
$cv->recv;

また、AnyEvent では Condition Variable に設定するコールバックの第一引数には Condition Variable 自身が渡される。さらに、コールバックの中での $cv->recv 呼び出しはブロックされないことが保証されている(See: perldoc AnyEvent) ことを利用すると、こういう書き方も可能になる。

my $cv = run_cmd [qw(sleep 10)];
print "now sleeping ....\n";

$cv->cb( sub { shift->recv; print "wake up\n"; } );

実際に AnyEvent::Util の POD を読むと、コールバックの中で recv を呼ぶ例があったりする。しかし、実際に上のプログラムを動作させると、"wake up" は出力されない。
run_cmd は内部で fork をした後、子プロセスで sleep(3) を実行するという動作をする。つまり、"wake up" が出力されないのは、子プロセスが sleep を実行している間に perl インタプリタが終了してしまっていることによるものだと思う。実際に ps コマンドの出力を見ると、sleep コマンドが終了していないにも関わらず、perl インタプリタは終了していることが分かる。

% ./anyevent-run_cmd-sleep.pl;
now sleeping ....
% ps -ef | grep perl          
% ps -ef | grep sleep         
  501   532     1   0   0:00.00 ttys000    0:00.00 sleep 10

というわけで、後段で必ずコールバックを実行したいときは、$cv->recv を外に出すなどして、Perl インタプリタが終了しないように注意しないといけないっぽいですね。

*1:実際には $cv->end 経由での send