需要一个可以正常终止的词法作用域动作的结束

发布于 2024-10-10 13:02:51 字数 1263 浏览 4 评论 0原文

我需要能够将动作添加到动作可能消失的词汇块的末尾。我需要正常抛出异常并能够正常捕获异常。

不幸的是,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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

你是我的挚爱i 2024-10-17 13:02:51

这在语义上合理吗?据我了解,你有这个(伪代码):

try {
    user_code(); # might throw
}
finally {
    clean_up(); # might throw
}

有两种可能性:

  • user_code()clean_up() 永远不会在同一次运行中抛出,在这种情况下您可以将其编写为顺序代码,而无需任何有趣的保护业务,它就会起作用。
  • user_code()clean_up() 可能在某些时候在同一次运行中抛出异常。

如果两个函数都可能抛出,那么就有两个活动异常。我不知道有什么语言可以处理当前抛出的多个活动异常,但我确信这是有充分理由的。 Perl 添加了(在清理中) 并使异常无法捕获; C++ 调用 terminate(), Java默默地丢弃原始异常,等等。

如果你刚刚从一个 eval,其中 user_code()cleanup() 都抛出异常,您希望在 $@?

通常这表明您需要在本地处理清理异常,也许通过忽略清理异常:

try {
    user_code();
}
finally {
    try {
        clean_up();
    }
    catch {
        # handle exception locally, cannot propagate further
    }
}

或者您需要选择一个异常在两者都抛出时忽略(这就是 DVK 的解决方案所做的;它忽略 user_code() 异常):

try {
    user_code();
}
catch {
    $user_except = $@;
}
try {
    cleanup();
}
catch {
    $cleanup_except = $@;
}
die $cleanup_except if $cleanup_except; # if both threw, this takes precedence
die $user_except if $user_except;

或者以某种方式将两个异常合并为一个异常对象:

try {
    user_code();
}
catch {
    try {
        clean_up();
    }
    catch {
        throw CompositeException; # combines user_code() and clean_up() exceptions
    }
    throw; # rethrow user_code() exception
}
clean_up();

我觉得应该有一种方法可以避免重复上面示例中的 clean_up() 行,但我想不到。

简而言之,如果不知道两个部分都抛出时您认为应该发生什么,您的问题就无法得到解答。

Is this semantically sound? From what I understand, you have this (in pseudocode):

try {
    user_code(); # might throw
}
finally {
    clean_up(); # might throw
}

There are two possibilities:

  • user_code() and clean_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() and clean_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++ calls terminate(), Java drops the original exception silently, etc etc.

If you have just come out of an eval in which both user_code() and cleanup() 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:

try {
    user_code();
}
finally {
    try {
        clean_up();
    }
    catch {
        # handle exception locally, cannot propagate further
    }
}

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):

try {
    user_code();
}
catch {
    $user_except = $@;
}
try {
    cleanup();
}
catch {
    $cleanup_except = $@;
}
die $cleanup_except if $cleanup_except; # if both threw, this takes precedence
die $user_except if $user_except;

or somehow combine the two exceptions into one exception object:

try {
    user_code();
}
catch {
    try {
        clean_up();
    }
    catch {
        throw CompositeException; # combines user_code() and clean_up() exceptions
    }
    throw; # rethrow user_code() exception
}
clean_up();

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.

放低过去 2024-10-17 13:02:51

更新:下面的方法似乎并不像埃里克所说的那样有效!

我将保留这个答案,以防有人可以将其调整为工作状态。

问题是:

我预计,一旦本地变量超出范围,将旧的全局值弹出回全局绑定变量将涉及对 FETCH/STORE 的调用,但不知何故,它只是默默地发生,而没有命中绑定机制(该问题与异常处理无关)。


Schwern - 我不是 100% 确定你可以使用领带技术(从 Abigail 的 Perlmonks 帖子)对于您的用例 - 这是我尝试做的事情,我认为您正在尝试做

use Test::More tests => 6;

my $guard_triggered = 0;
sub user_cleanup { $guard_triggered++; die "En guarde!" }; # Line 4;
sub TIESCALAR {bless \(my $dummy) => shift}
sub FETCH     { user_cleanup(); }
sub STORE     {1;}
our $guard;
tie $guard => __PACKAGE__; # I don't think the actual value matters

sub x {
    my $x = 1; # Setup
    local $guard = "in x";
    my $y = 2; #user_code;
}

sub x2 {
    my $x = 1; # Setup
    local $guard = "in x2";
    die "you bastard"; #user_code;
}

ok !eval {
    x();
}, "the guard died";
is $@, "En guarde! at $0 line 4.\n",    "with the right error message";
is $guard_triggered, 1,                 "the guard worked";

ok !eval {
    x2();
}, "the guard died";
is $@, "En guarde! at $0 line 4.\n",    "with the right error message";
is $guard_triggered, 2,                 "the guard worked";

输出:

1..6
ok 1 - the guard died
ok 2 - with the right error message
ok 3 - the guard worked
ok 4 - the guard died
ok 5 - with the right error message
ok 6 - the guard worked

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

use Test::More tests => 6;

my $guard_triggered = 0;
sub user_cleanup { $guard_triggered++; die "En guarde!" }; # Line 4;
sub TIESCALAR {bless \(my $dummy) => shift}
sub FETCH     { user_cleanup(); }
sub STORE     {1;}
our $guard;
tie $guard => __PACKAGE__; # I don't think the actual value matters

sub x {
    my $x = 1; # Setup
    local $guard = "in x";
    my $y = 2; #user_code;
}

sub x2 {
    my $x = 1; # Setup
    local $guard = "in x2";
    die "you bastard"; #user_code;
}

ok !eval {
    x();
}, "the guard died";
is $@, "En guarde! at $0 line 4.\n",    "with the right error message";
is $guard_triggered, 1,                 "the guard worked";

ok !eval {
    x2();
}, "the guard died";
is $@, "En guarde! at $0 line 4.\n",    "with the right error message";
is $guard_triggered, 2,                 "the guard worked";

OUTPUT:

1..6
ok 1 - the guard died
ok 2 - with the right error message
ok 3 - the guard worked
ok 4 - the guard died
ok 5 - with the right error message
ok 6 - the guard worked
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文