Нужен конец лексической области действия, которая может умереть нормально - PullRequest
7 голосов
/ 06 января 2011

Мне нужна возможность добавлять действия в конец лексического блока, где действие может умереть.И мне нужно, чтобы исключение было брошено нормально и чтобы его можно было поймать нормально.

К сожалению, особые случаи Perl исключают во время DESTROY, добавляя к сообщению "(в процессе очистки)" и делая их недоступными для поиска.Например:

{
    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 из-за способа, который изменяет стек.

Ответы [ 2 ]

2 голосов
/ 06 января 2011

Это семантически звучит?Из того, что я понимаю, у вас есть это (в псевдокоде):

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

Есть две возможности:

  • user_code() и clean_up() никогда не будет выбрасывать в одном запуске, в этом случае вы можете просто написать его как последовательный код без каких-либо забавных мер охраны, и он будет работать.
  • user_code() и clean_up() могут в какой-то момент оба выполнить один и тот же прогон.

Если обе функции могут выдавать, у вас есть два активных исключения.Я не знаю какого-либо языка, который может обрабатывать несколько активных в настоящее время генерируемых исключений, и я уверен, что для этого есть веская причина.Perl добавляет (in cleanup) и делает исключение недоступным; C ++ звонки terminate(), Java отбрасывает исходное исключение без предупреждения и т. Д. И т. Д.

Если вы только что вышли из eval, в котором оба user_code() и cleanup() выдали исключения, что вы ожидаете найти в $@?

Обычно это означает, что вам нужно обрабатывать исключение очистки локально, возможно, игнорируя исключение очистки:

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

или вам нужно выбрать исключение, которое будет игнорироваться, когда оба throw (что и делает решение 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() в приведенном выше примере, но я не могу думать об этом.

Короче говоря, беззная, что, по вашему мнению, должно произойти, когда обе части бросают, ваша проблема не может быть решена.

0 голосов
/ 06 января 2011

ОБНОВЛЕНИЕ: Кажется, что описанный ниже подход не работает так, как написано Эриком!

Я оставляю этот ответ на случай, если кто-то может привести его в рабочее состояние.

Проблема в следующем:

Я ожидал, что возврат старого глобального значения обратно в глобальную связанную переменную, как только локальное выйдет из области видимости, повлечет за собой вызов FETCH /STORE, но каким-то образом это происходит тихо, не затрагивая связанный механизм (проблема не имеет отношения к обработке исключений).


Шверн - я не уверен на 100%, что вы можете использовать технику связывания (украдено из сообщение Perlmonks от Abigail ) для вашего случая использования - вот моя попытка сделать то, что, как я думаю, вы пытались сделать

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
...