base (< 2.14) が $SIG{__DIE__} を破壊するのではまった

たとえば,

#!/usr/bin/perl

use strict;
use warnings;

local $SIG{__DIE__} = sub {
    warn "signal handled";
    die $_[0];
};

require Foo;
die "foo";

こんなスクリプトがあったとして,実行すると,

signal handled at test.pl line 7.
foo at test.pl line 12.

みたいになる(それなりの package Foo があったとして)。


ところが,その package Foo の中身が

package Foo;

use strict;
use warnings;

use base qw( URI );     # なんでもよい

のようになっていた場合。つまり,(パッケージ指定の)use base を使っているときに,実行すると,

foo at test.pl line 12.

みたくなって,__DIE__ シグナルハンドラが呼ばれない。

なんでーと思ったら,RT にも登録されてた。1年も前だけど。⇒ Bug #30375 for base: base.pm doesn't honor already installed $SIG{__DIE__} handlers


これ,base(ただし 2.14 未満)のコードが下記のようになってるからっぽい。

            my $sigdie;
            {
                local $SIG{__DIE__};
                eval "require $base";
                # Only ignore "Can't locate" errors from our eval require.
                # Other fatal errors (syntax etc) must be reported.
                die if $@ && $@ !~ /^Can't locate .*? at \(eval /;

                # ...... snip snip snip ......

                $sigdie = $SIG{__DIE__};
            }
            # Make sure a global $SIG{__DIE__} makes it out of the localization.

            $SIG{__DIE__} = $sigdie if defined $sigdie;

素人目には,スコープくぎって $SIG{__DIE__} を localization してるから,わざわざ設定しなおす必要ないじゃんとか思うんだけど,それなりに理由があるのかな?

んで,どうも Perl 5.10 未満だと,local $SIG{__DIE__}; で(値を設定せずに) localization しても $SIG{__DIE__} は defined となってしまうみたい。普通の local なら,ちゃんと undef になるんですけどね。


さっき 2.14 未満だと,って書いたとおり,実は base 2.14 で fixed されてる。(Perl 5.10 だと露呈しなかったのかな?)

2.14
    - fix problem with SIGDIE on perls < 5.10
http://search.cpan.org/src/RGARCIA/base-2.14/Changes

該当部分が下記のように修正されてる。

                $sigdie = $SIG{__DIE__} || undef;

ここだけ違う。それでも Perl のスコープ& local による localization に頼らないのは,やっぱりなんらかの理由があるんでしょうね。



以下おまけ。

当初 base-2.14 に気づいてなかったので,それを fix するモジュールを書いてた。

package base::fix;

use strict;
use warnings;

use base;

BEGIN {
    my $original = \&{'base::import'};

    my $sub = sub {
        {   
            local $SIG{__DIE__} = $SIG{__DIE__} if defined $SIG{__DIE__};
            local $Carp::CarpLevel = $Carp::CarpLevel + 1;
            return &$original(@_);
        }
    };

    no strict   'refs';
    no warnings 'redefine';
    *{'base::import'} = $sub;
}

1;

ほんとは goto で飛ばせればもっとスマートに書けるんだけど,goto 使うと local による localization が働かなくなるんですね。うーむ。

あとこんなパッチあてしなくても,parent 使えばいいのかな?このモジュールの立ち位置がよくわからんです。