追記
牧さんがhttp://mt.endeworks.jp/d-6/2009/11/perl-4.htmlで詳細に書いてくれてるのでそっちを見るといいです。 lestrrat++
シグナルハンドラの中で SIGINT とかを無効にしたい - 刺身☆ブーメランのはてなダイアリー で「うまくいかない」と書いてたやつの解決編。のつもり。
やりたかったことは、「時間がかかる処理を実行中の Perl スクリプトが SIGINT を受け取ったとき、すでに実行中の処理が終わるまで待ってから死んで欲しい」というもの。
コードそのものは実は大してかわってない。どうも、 SIGINT などが送られたときに「Perl のステートメントひとつの単位で処理に割り込みが入る」ような挙動になってるらしく、終わるまで待って欲しい処理(以下の例だと foo() ないし bar() 関数の実行)の中が例えば sleep 5; のようになっていると、「五秒待つ」というのが Perl にとってアトミックな処理なので、丸ごと割り込まれてしまう(・・・ので、先日のエントリの例だと三秒待ってから終了して欲しいのに、実際には三秒経たずに終了してしまう)、という風になっているようだ(説明が意味不明なのは僕も正確に理解しているわけではなく、 id:nabokov に教えてもらった通りに書いたら動いた!とりあえずメモっとこ!・・・だからです)
#!/usr/bin/env perl use strict; use DateTime; our $cnt = 0; our $int = 0; $SIG{INT} = sub { warn sprintf('sigint received at %s.', DateTime->now(time_zone => 'local')); $int++; }; while (1) { warn 'CNT: '. $cnt; foo(); warn 'CNT: '. $cnt; if ($int) { warn sprintf('interrupted at %s.', DateTime->now(time_zone => 'local')); exit 1; } } sub foo { warn 'start foo()'; sleep 1;sleep 1;sleep 1;sleep 1;sleep 1; # 5 sec for (1..50) { $cnt++; } bar(); sleep 1;sleep 1;sleep 1;sleep 1;sleep 1; # 5 sec warn 'end foo()'; } sub bar { warn 'start bar()'; sleep 1;sleep 1;sleep 1;sleep 1;sleep 1; # 5 sec for (1..50) { $cnt++; } sleep 1;sleep 1;sleep 1;sleep 1;sleep 1; # 5 sec warn 'end bar()'; }
実行結果はこんな風になる。実行中に何度か Ctrl-C を受け取っているけどそのまま処理が続いてることがわかる。
$ ./sigint.pl CNT: 0 at ./sigint.pl line 14. start foo() at ./sigint.pl line 24. ^Csigint received at 2009-11-24T19:19:25. at ./sigint.pl line 9. start bar() at ./sigint.pl line 34. ^Csigint received at 2009-11-24T19:19:32. at ./sigint.pl line 9. end bar() at ./sigint.pl line 40. ^Csigint received at 2009-11-24T19:19:40. at ./sigint.pl line 9. end foo() at ./sigint.pl line 31. CNT: 100 at ./sigint.pl line 16. interrupted at 2009-11-24T19:19:44. at ./sigint.pl line 18.