Beefy Boxes and Bandwidth Generously Provided by pair Networks
Welcome to the Monastery
 
PerlMonks  

eval, DESTROY, die and $@ - educational couple of hours

by lestrrat (Deacon)
on Mar 28, 2002 at 02:33 UTC ( #154876=perlmeditation: print w/ replies, xml ) Need Help??

Perhaps this is something of a common knowledge, but this bit me really hard, so I'd like to share my experience of debugging a behavior that was totally unexpected for me with eval,die,DESTROY, and $@...

Recently I was playing around with SOAP, and one of the particular SOAP objects used a DBI wrapper that I wrote internally. The SOAP object code looked something like this:

## names of modules/subs changed for convenience... package MySOAP; use strict; use My::DBIWrapper; sub foo { my $dbh; eval { $dbh = My::DBIWrapper->connect; ...bunch of processing... }; if( my $err = $@ ) { eval{ $dbh->disconnect }; ## just make sure die $err; ## reraise so SOAP->fault returns true } }

And the DBI wrapper went something like this:

## I really didn't want to subclass DBI, so I just ## encapsulate a database handle and proxy all ## subroutine calls that My::DBIWrapper doesn't ## understand via AUTOLOAD. Other subs have been omitted for bravit +y package My::DBIWrapper; use strict; our( $AUTOLOAD ); sub connect { my $class = shift; my $self = { dbhandle => undef }; bless $self, $class; $self->dbhandle( DBI->connect( .... ) ); return $self; } sub AUTOLOAD { ( my $subname = $AUTOLOAD ) =~ s/^.*:://; if( eval{ $_[0]->dbhandle && $_[0]->dbhandle->can( $subname ) ) +{ eval "sub $subname { shift->dbhandle->$subname(\@_) }"; die $@ if $@; goto &$AUTOLOAD; } Carp::croak( "Undefined subroutine $AUTOLOAD called" ); } sub DESTROY { my $self = shift; my $dbh = $self->dbhandle; if( $dbh ) { eval{ $dbh->disconnect }; } } 1;

So what is the problem? The problem was that when there's a call to die() in the eval{} block of the SOAP object, while I do get into to the if( $@ )... block, the SOAP object did not return any faults.

I verified and verified that the die() call was properly propagated within the SOAP object. I knew that I was passing the correct arguments to the final die() call which should have propated the failure to the SOAP results as well... But it wasn't working, so I almost concluded that it had to be some sort of SOAP::LIte bug. Boy was I wrong

Upon changing a bunch of small things here and there, I found out that as long as I didn't instantiate MY::DBIWrapper and used DBI instead, everything worked fine. I still failed to see why...

then I started munging with the "special" subs in the My::DBIWrapper object. At which point I realized that if the eval{} in DESTROY is removed, everything works! Aaaaaaaahhhhhhhh.

I finally got what this was... yes, $@ was getting reassigned to '' in the DESTROY sub before SOAP could pick it up! To illustrate how this works, here's an example:

1: package Foo; 2: sub DESTROY 3: { 4: $@ = ''; 5: ## local $@ = ''; <-- this will work 6: } 7: 8: package main; 9: eval { 10: my $foo = bless {}, 'Foo'; 11: die; 12: }; 13: if( $@ ) { 14: die $@; 15: }

So this is how it works. The first propagation of die() happens as expected from line 11, which is then caught at line 13. The problem is what happens between line 14 and the end of the execution of this code. At some point before completely exiting out of this scope, the DESTROY sub of package Foo gets called. If the DESTROY code happens to set $@ to an empty value, the program exits as if nothing has happened! However, if you localize $@, then the original $@ is untouched, so the final die() exits with an error as expected.

What's really peculiar about this actually completely "resets" the exception. Observe the difference in the exit status of the above code, with and without a localized $@:

## with local $@ me@myhost> perl ~/test.pl; echo $? Died at /home/me/test.pl line 14 255 ## without local $@ me@myhost> perl ~/test.pl; echo $? 0

I was completely unaware of this -- that exceptions are solely based on the fact that $@ is set to some value, and that $@ was a true global. I somehow thought that $@ would be magical, but now that I think about it wouldn't make sense... Anyway, all I can say is wow, and I'm glad I caught it before I put my code into production.

So I guess my lesson for the day is this: For any "special" sub in a module, it's probably best to localize any global variable that you may alter during that block of code.

That was some educational couple of hours. For those of you who know about this stuff, please feel free to let me know if my understanding of how this works is wrong. And I hope this sheds some light to others who are still on the way to fully undestanding how Perl works

Comment on eval, DESTROY, die and $@ - educational couple of hours
Select or Download Code
Re: eval, DESTROY, die and $@ - educational couple of hours
by Anonymous Monk on Jul 16, 2014 at 08:57 UTC
    I would humbly like to report that more than a decade later, dear lestrrat, the effort you put in sharing the results of your educational couple hours proved useful to an established codebase, somewhere in France, where the following patch was just applied to a DESTROY method:
    + local $@; # Allow exceptions to bubble all the way up to burst as + a SOAP Fault. See http://www.perlmonks.org/?node_id=154876
    So, thank you.

Log In?
Username:
Password:

What's my password?
Create A New User
Node Status?
node history
Node Type: perlmeditation [id://154876]
Approved by root
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others examining the Monastery: (12)
As of 2014-10-22 13:26 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    For retirement, I am banking on:










    Results (118 votes), past polls