需要一个可以正常终止的词法作用域动作的结束
我需要能够将动作添加到动作可能消失的词汇块的末尾。我需要正常抛出异常并能够正常捕获异常。
不幸的是,Perl 特殊情况在 DESTROY 期间出现异常,都是通过在消息中添加“(in cleanup)”并使它们无法捕获。例如:
{
package Guard;
use strict;
use warnings;
sub new {
my $class = shift;
my $code = shift;
return bless $code, $class;
}
sub DESTROY {
my $self = shift;
$self->();
}
}
use Test::More tests => 2;
my $guard_triggered = 0;
ok !eval {
my $guard = Guard->new(
#line 24
sub {
$guard_triggered++;
die "En guarde!"
}
);
1;
}, "the guard died";
is $@, "En guarde! at $@ line 24\n", "with the right error message";
is $guard_triggered, 1, "the guard worked";
我希望这件事能够过去。目前,异常已被 eval 完全吞没。
这是针对 Test::Builder2 的,所以除了纯 Perl 之外我不能使用任何东西。
根本问题是我有这样的代码:
{
$self->setup;
$user_code->();
$self->cleanup;
}
即使 $user_code 死亡,也必须进行清理,否则 $self 会进入奇怪的状态。所以我这样做了:
{
$self->setup;
my $guard = Guard->new(sub { $self->cleanup });
$user_code->();
}
复杂性的出现是因为清理运行任意用户代码,并且这是该代码将死亡的用例。我希望守卫能够捕获并且不改变该异常。
由于改变堆栈的方式,我避免将所有内容包装在 eval 块中。
I need the ability to add actions to the end of a lexical block where the action might die. And I need the exception to be thrown normally and be able to be caught normally.
Unfortunately, Perl special cases exceptions during DESTROY both by adding "(in cleanup)" to the message and making them untrappable. For example:
{
package Guard;
use strict;
use warnings;
sub new {
my $class = shift;
my $code = shift;
return bless $code, $class;
}
sub DESTROY {
my $self = shift;
$self->();
}
}
use Test::More tests => 2;
my $guard_triggered = 0;
ok !eval {
my $guard = Guard->new(
#line 24
sub {
$guard_triggered++;
die "En guarde!"
}
);
1;
}, "the guard died";
is $@, "En guarde! at $@ line 24\n", "with the right error message";
is $guard_triggered, 1, "the guard worked";
I want that to pass. Currently the exception is totally swallowed by the eval.
This is for Test::Builder2, so I cannot use anything but pure Perl.
The underlying issue is I have code like this:
{
$self->setup;
$user_code->();
$self->cleanup;
}
That cleanup must happen even if the $user_code dies, else $self gets into a weird state. So I did this:
{
$self->setup;
my $guard = Guard->new(sub { $self->cleanup });
$user_code->();
}
The complexity comes because the cleanup runs arbitrary user code and it is a use case where that code will die. I expect that exception to be trappable and unaltered by the guard.
I'm avoiding wrapping everything in eval blocks because of the way that alters the stack.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(2)
这在语义上合理吗?据我了解,你有这个(伪代码):
有两种可能性:
user_code()
和clean_up()
永远不会在同一次运行中抛出,在这种情况下您可以将其编写为顺序代码,而无需任何有趣的保护业务,它就会起作用。user_code()
和clean_up()
可能在某些时候在同一次运行中抛出异常。如果两个函数都可能抛出,那么就有两个活动异常。我不知道有什么语言可以处理当前抛出的多个活动异常,但我确信这是有充分理由的。 Perl 添加了
(在清理中)
并使异常无法捕获; C++ 调用terminate()
, Java默默地丢弃原始异常,等等。如果你刚刚从一个
eval
,其中user_code()
和cleanup()
都抛出异常,您希望在$@?
通常这表明您需要在本地处理清理异常,也许通过忽略清理异常:
或者您需要选择一个异常在两者都抛出时忽略(这就是 DVK 的解决方案所做的;它忽略 user_code() 异常):
或者以某种方式将两个异常合并为一个异常对象:
我觉得应该有一种方法可以避免重复上面示例中的
clean_up()
行,但我想不到。简而言之,如果不知道两个部分都抛出时您认为应该发生什么,您的问题就无法得到解答。
Is this semantically sound? From what I understand, you have this (in pseudocode):
There are two possibilities:
user_code()
andclean_up()
will never throw in the same run, in which case you can just write it as sequential code without any funny guard business and it will work.user_code()
andclean_up()
may, at some point, both throw in the same run.If both functions may throw, then you have two active exceptions. I don't know any language which can handle multiple active currently thrown exceptions, and I'm sure there's a good reason for this. Perl adds
(in cleanup)
and makes the exception untrappable; C++ callsterminate()
, Java drops the original exception silently, etc etc.If you have just come out of an
eval
in which bothuser_code()
andcleanup()
threw exceptions, what do you expect to find in$@
?Usually this indicates you need to handle the cleanup exception locally, perhaps by ignoring the cleanup exception:
or you need to choose an exception to ignore when both throw (which is what DVK's solution does; it ignores the user_code() exception):
or somehow combine the two exceptions into one exception object:
I feel there should be a way to avoid repeating the
clean_up()
line in the above example, but I can't think of it.In short, without knowing what you think should happen when both parts throw, your problem cannot be answered.
更新:下面的方法似乎并不像埃里克所说的那样有效!
我将保留这个答案,以防有人可以将其调整为工作状态。
问题是:
我预计,一旦本地变量超出范围,将旧的全局值弹出回全局绑定变量将涉及对 FETCH/STORE 的调用,但不知何故,它只是默默地发生,而没有命中绑定机制(该问题与异常处理无关)。
Schwern - 我不是 100% 确定你可以使用领带技术(从 Abigail 的 Perlmonks 帖子)对于您的用例 - 这是我尝试做的事情,我认为您正在尝试做
输出:
UPDATE: The approach below doesn't seem to work as written as Eric noted!.
I'm leaving this answer up in case someone can wrangle it into working shape.
The problem is:
I expected that popping old global value back onto the global tied variable once the local one goes out of scope will involve a call to FETCH/STORE, but somehow it just happens silently without hitting the tied mechanism (the issue is irrelevant to exception handling).
Schwern - I'm not 100% sure you can use the tie technique (stolen from Perlmonks post by Abigail) for your use case - here's my attempt to do what I think you were trying to do
OUTPUT: