考虑这个例子:
#!/usr/bin/perl
use strict;
use warnings;
require 5.006_001;
use Graphics::Magick;
use sigtrap qw( die any );
sub try { my ($err) = @_; die("$err\n") if $err; }
my $image = Graphics::Magick->new;
sub process {
my ($file) = @_;
my $result = eval {
kill 'ABRT',$$ if $file eq "magic";
try $image->Read($file);
};
if (defined $result) { print "Yay :)\n"; }
else { warn($@); }
}
process "good.gif"; # works
process "MINN.XK4"; # needs font that doesn't exist
process "magic"; # trigger abort manually
process "foo"; # file doesn't exist
我原以为这可以保护主程序免受模块中问题的影响,而且大多数情况下确实如此。然而,我最近遇到了一种情况,模块的某些实现触发断言失败。
在大多数系统上,我会看到类似的内容:
Yay :)
Exception 405: Unable to read font (/usr/share/fonts/type1/gsfonts/n021003l.pfb)
Caught a SIGABRT at ./example line 18.
Exception 430: Unable to open file (foo)
然而,事实证明这也是可能的:
Yay :)
perl: magick/draw.c:1777: DrawSetFont: Assertion `font_name != (const char *) NULL' failed.
Aborted
我应该如何重写以更好地将主程序与模块中的故障隔离?
多年来我似乎一直误解
eval
的用途,而且显然我并不真正了解信号处理。为什么模块中的assert()调用触发的ABORT/IOT与我上面触发的不同?我的 abort(3) 手册页说:
abort() 函数首先解锁 SIGABRT 信号,然后为调用进程引发该信号(就像调用 raise(3) 一样)。这会导致进程异常终止,除非捕获到 SIGABRT 信号并且信号处理程序不会返回(请参阅 longjmp(3))。
那么也许 Perl 有一种“不返回”的方法?
.so
告诉我:
当即将抛出致命异常时,将调用perldoc perlvar
指示的例程。错误消息作为第一个参数传递。当“
”钩子例程返回时,异常处理将像没有钩子时一样继续,除非钩子例程本身通过“$SIG{__DIE__}
”、循环退出或“__DIE__
”退出”。 “goto &sub
”处理程序在调用期间被显式禁用,因此您可能会死于“die()
”处理程序。对于“__DIE__
”也是如此。 但我不知道如何使用这些信息。__DIE__
(而不是 __WARN__
)为其安装钩子来捕获它
eval
如
perl -wE'say $$; kill "ABRT", $$; say "bye"'
但是
17807
Aborted (core dumped)
在信号中幸存
perl -wE'say $$; $SIG{ABRT} = sub { say "got ya" }; kill "ABRT", $$; say "bye"'
但我不确定我是否理解了问题的全部要点。但如果您的程序确实被
17832
got ya
bye
杀死,那么安装
SIGABRT
将使您免受这种情况。