问题
I have a piece of Perl code somewhat like the following (strongly simplified): There are some levels of nested subroutine calls (actually, methods), and some of the inner ones do their own exception handling:
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!" }
Now I want to change that code so that it does the following:
print a full stack trace for every exception that "bubbles up" all the way to the outermost level (
sub outer
). Specifically, the stack trace should not stop at the first level of "eval { }
".Not having to change the the implementation of any of the inner levels.
Right now, the way I do this is to install a localized __DIE__
handler inside the outer
sub:
use Devel::StackTrace;
sub outer {
local $SIG{__DIE__} = sub {
my $error = shift;
my $trace = Devel::StackTrace->new;
print "Error: $error\n",
"Stack Trace:\n",
$trace->as_string;
};
middle();
}
[EDIT: I made a mistake, the code above actually doesn't work the way I want, it actually bypasses the exception handling of the middle
sub. So I guess the question should really be: Is the behaviour I want even possible?]
This works perfectly, the only problem is that, if I understand the docs correctly, it relies on behaviour that is explicitly deprecated, namely the fact that __DIE__
handlers are triggered even for "die
"s inside of "eval { }
"s, which they really shouldn't. Both perlvar
and perlsub
state that this behaviour might be removed in future versions of Perl.
Is there another way I can achieve this without relying on deprecated behaviour, or is it save to rely on even if the docs say otherwise?
回答1:
It is not safe to rely on anything that the documentation says is deprecated. The behavior could (and likely will) change in a future release. Relying on deprecated behavior locks you into the version of Perl you're running today.
Unfortunately, I don't see a way around this that meets your criteria. The "right" solution is to modify the inner methods to call Carp::confess
instead of die
and drop the custom $SIG{__DIE__}
handler.
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
Since you're dieing anyway, you may not need to trap the call to inner()
. (You don't in your example, your actual code may differ.)
In your example you're trying to return data via $@
. You can't do that. Use
my $x = eval { inner(@_) };
instead. (I'm assuming this is just an error in simplifying the code enough to post it here.)
回答2:
UPDATE: I changed the code to override die
globally so that exceptions from other packages can be caught as well.
Does the following do what you want?
#!/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!" }
回答3:
Note that overriding die
will only catch actual calls to die
, not Perl errors like dereferencing undef
.
I don't think the general case is possible; the entire point of eval
is to consume errors. You MIGHT be able to rely on the deprecated behavior for exactly this reason: there's no other way to do this at the moment. But I can't find any reasonable way to get a stack trace in every case without potentially breaking whatever error-handling code already exists however far down the stack.
来源:https://stackoverflow.com/questions/971273/perl-sig-die-eval-and-stack-trace