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!" }
现在我想更改该代码,以便它执行以下操作:
为每个一直“冒泡”到最外层(
subouter
)的异常打印完整的堆栈跟踪。 具体来说,堆栈跟踪不应停止在“eval { }
”的第一级。不必更改任何内部级别的实现。
现在,我执行此操作的方法是在 outer
子内部安装本地化的 __DIE__
处理程序:
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();
}
[编辑:我犯了一个错误,上面的代码实际上不按照我想要的方式工作,它实际上绕过了middle
子的异常处理。 所以我想问题实际上应该是:我想要的行为是否可能?]
这完美地工作,唯一的问题是,如果我正确理解文档,它依赖于明确弃用的行为,即即使“eval { }”内的“die
”也会触发 __DIE__
处理程序,它们确实应该这样做” t。 perlvar
和 perlsub
都声明此行为可能会在 Perl 的未来版本中删除。
有没有另一种方法可以在不依赖已弃用的行为的情况下实现此目的,或者即使文档另有说明,也可以依赖它吗?
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?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
更新:我更改了代码以全局覆盖
die
,以便也可以捕获来自其他包的异常。以下内容是否符合您的要求?
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?
依赖文档中指出已弃用的任何内容都是不安全的。 该行为可能(并且可能会)在未来版本中发生变化。 依赖已弃用的行为会将您锁定在当前运行的 Perl 版本中。
不幸的是,我没有找到符合您标准的解决方法。 “正确”的解决方案是修改内部方法以调用
Carp::confess
而不是die
并删除自定义$SIG{__DIE__}
处理程序。既然你无论如何都会死,你可能不需要捕获对
inner()
的调用。 (在您的示例中,您的实际代码可能有所不同。)在您的示例中,您尝试通过
$@
返回数据。 你不能那样做。 代替使用。 (我假设这只是将代码简化到足以将其发布到此处的错误。)
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 ofdie
and drop the custom$SIG{__DIE__}
handler.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. Useinstead. (I'm assuming this is just an error in simplifying the code enough to post it here.)
请注意,覆盖
die
只会捕获对die
的实际调用,不会像解引用undef
这样的 Perl 错误。我认为一般情况是不可能的;
eval
的全部意义在于消耗错误。 正是出于这个原因,您可能能够依赖已弃用的行为:目前没有其他方法可以做到这一点。 但我找不到任何合理的方法来在每种情况下获取堆栈跟踪,而不会潜在地破坏堆栈中已经存在的任何错误处理代码。Note that overriding
die
will only catch actual calls todie
, not Perl errors like dereferencingundef
.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.