It is necessary to end the action of the lexical scope, which may die normally

I need the ability to add actions to the end of the lexical block, where the action may die. And I need the exception to be thrown normally and caught normally.

Unfortunately, exceptions are made for special cases of Perl during DESTROY, both by adding "(when clearing)" to the message, as well as their impossibility. 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 this to go away. Currently, the exception is completely absorbed by eval.

This is for Test :: Builder2, so I can't use anything but pure Perl.

The main problem is that I have code like this:

 { $self->setup; $user_code->(); $self->cleanup; } 

This cleanup should happen even if $ user_code dies, otherwise $ self gets into a weird state. So I did this:

 { $self->setup; my $guard = Guard->new(sub { $self->cleanup }); $user_code->(); } 

The difficulty arises from the fact that cleaning launches arbitrary user code, and this is a precedent when this code dies. I expect this exception to be hidden and not changed by the guard.

I avoid wrapping everything in eval blocks because of the way the stack changes.

+7
source share
2 answers

Is it semantically sound? As far as I understand, you have this (in pseudo-code):

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

There are two possibilities:

  • user_code() and clean_up() will never run the same run, in which case you can simply write it as sequential code without any funny guard business, and it will work.
  • user_code() and clean_up() may at some point abandon the same run.

If both functions can throw, you have two active exceptions. I do not know a single language that can handle several currently active exceptions, and I am sure there is a good reason for this. Perl adds (in cleanup) and makes an exception impossible; C ++ calls terminate() , Java automatically disables the original exception , etc.

If you just exited eval in which both user_code() and cleanup() user_code() exceptions, what do you expect to find in $@ ?

This usually means that you need to handle the cleanup exception locally, possibly 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 the DVK solution does, ignore 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 in any way 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 believe that there should be a way to avoid repeating the clean_up() in the above example, but I can't think about it.

In short, not knowing what, in your opinion, should happen when both parts are thrown, your problem cannot be solved.

+2
source

UPDATE: the approach below does not work as it is written by Eric!

I leave this answer in case someone can interrupt it in working form.

The problem is this:

I expected that repeating the old global value back to the global bound variable, if the local output goes out of scope, would include a FETCH / STORE call, but somehow it just happens without affecting the bound mechanism (the problem is not related to exception handling) .


Schwern - I'm not 100% sure that you can use the communication technique (stolen from the Perlmonks post from Abigail ) for your use case - here is my attempt to do what I think you tried 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 
0
source

All Articles