Beefy Boxes and Bandwidth Generously Provided by pair Networks
Think about Loose Coupling
 
PerlMonks  

comment on

( [id://3333]=superdoc: 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


In reply to eval, DESTROY, die and $@ - educational couple of hours by lestrrat

Title:
Use:  <p> text here (a paragraph) </p>
and:  <code> code here </code>
to format your post; it's "PerlMonks-approved HTML":



  • Are you posting in the right place? Check out Where do I post X? to know for sure.
  • Posts may use any of the Perl Monks Approved HTML tags. Currently these include the following:
    <code> <a> <b> <big> <blockquote> <br /> <dd> <dl> <dt> <em> <font> <h1> <h2> <h3> <h4> <h5> <h6> <hr /> <i> <li> <nbsp> <ol> <p> <small> <strike> <strong> <sub> <sup> <table> <td> <th> <tr> <tt> <u> <ul>
  • Snippets of code should be wrapped in <code> tags not <pre> tags. In fact, <pre> tags should generally be avoided. If they must be used, extreme care should be taken to ensure that their contents do not have long lines (<70 chars), in order to prevent horizontal scrolling (and possible janitor intervention).
  • Want more info? How to link or How to display code and escape characters are good places to start.
Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others rifling through the Monastery: (5)
As of 2024-04-19 22:48 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found