Perl: $SIG{DIE, eval {} и трассировка стека
у меня есть фрагмент кода Perl, несколько похожий на следующий (сильно упрощенный): есть некоторые уровни вложенных вызовов подпрограмм (на самом деле, методы), и некоторые из внутренних выполняют свою собственную обработку исключений:
sub outer { middle() }
sub middle {
eval { inner() };
if ( my $x = $@ ) { # caught exception
if (ref $x eq 'ARRAY') {
print "we can handle this ...";
}
else {
die $x; # rethrow
}
}
}
sub inner { die "OH NOES!" }
теперь я хочу изменить этот код, чтобы он делал следующее:
печать полной трассировки стека для каждого исключения, которое "пузырится" до самого внешнего уровня (
sub outer
). В частности, трассировка стека не остановка на первом уровне "eval { }
".нет необходимости изменять реализацию любого из внутренних уровней.
прямо сейчас, как я это делаю, это установить локализованный __DIE__
обработчик внутри outer
sub:
use Devel::StackTrace;
sub outer {
local $SIG{__DIE__} = sub {
my $error = shift;
my $trace = Devel::StackTrace->new;
print "Error: $errorn",
"Stack Trace:n",
$trace->as_string;
};
middle();
}
[редактировать: я сделал ошибку, код выше на самом деле не работать так, как я хочу, он фактически обходит обработку исключений middle
sub. Поэтому я думаю, что вопрос действительно должен быть: возможно ли поведение, которое я хочу?]
работает отлично, единственная проблема заключается в том, что, если я правильно понимаю документы, он полагается на поведение, которое явно устарело, а именно тот факт, что __DIE__
обработчики срабатывают даже для "die
" s внутри "eval { }
s, чего они действительно не должны. Оба!--10--> и perlsub
укажите, что это поведение может быть удалено в будущих версиях язык Perl.
есть ли другой способ достичь этого, не полагаясь на устаревшее поведение, или это сохранить, чтобы полагаться, даже если документы говорят иначе?
3 ответов
это не безопасно полагаться на все, что говорится в документации, устарело. Поведение может (и скорее всего) измениться в будущем выпуске. Использование устаревшего поведения блокирует вас в версии Perl, которую вы используете сегодня.
к сожалению, я не вижу способ, который соответствует вашим критериям. "Правильным" решением является изменение внутренних методов для вызова Carp::confess
вместо die
и удалить пользовательские $SIG{__DIE__}
обработчик.
use strict;
use warnings;
use Carp qw'confess';
outer();
sub outer { middle(@_) }
sub middle { eval { inner() }; die $@ if $@ }
sub inner { confess("OH NOES!") }
__END__
OH NOES! at c:\temp\foo.pl line 11
main::inner() called at c:\temp\foo.pl line 9
eval {...} called at c:\temp\foo.pl line 9
main::middle() called at c:\temp\foo.pl line 7
main::outer() called at c:\temp\foo.pl line 5
поскольку вы все равно умираете, вам может не понадобиться ловить вызов inner()
. (Вы не в своем примере, ваш фактический код может отличаться.)
в вашем примере вы пытаетесь вернуть данные через $@
. Ты не можешь этого сделать. Использовать
my $x = eval { inner(@_) };
вместо. (Я предполагаю, что это просто ошибка в упрощении кода достаточно, чтобы разместить его здесь.)
обновление: Я изменил код, чтобы переопределить die
глобально, так что исключения из других пакетов также могут быть пойманы.
делает ли следующее То, что вы хотите?
#!/usr/bin/perl
use strict;
use warnings;
use Devel::StackTrace;
use ex::override GLOBAL_die => sub {
local *__ANON__ = "custom_die";
warn (
'Error: ', @_, "\n",
"Stack trace:\n",
Devel::StackTrace->new(no_refs => 1)->as_string, "\n",
);
exit 1;
};
use M; # dummy module to functions dying in other modules
outer();
sub outer {
middle( @_ );
M::n(); # M::n dies
}
sub middle {
eval { inner(@_) };
if ( my $x = $@ ) { # caught exception
if (ref $x eq 'ARRAY') {
print "we can handle this ...";
}
else {
die $x; # rethrow
}
}
}
sub inner { die "OH NOES!" }
обратите внимание, что переопределение die
будет ловить только фактические вызовы die
, не ошибки Perl, такие как разыменование undef
.
Я не думаю, что общий случай возможен; весь смысл eval
потреблять ошибок. Вы можете положиться на осужденное поведение именно по этой причине: на данный момент нет другого способа сделать это. Но я не могу найти никакого разумного способа получить трассировку стека в каждом случае, не нарушая ничего код обработки ошибок уже существует, однако далеко вниз по стеку.