Beefy Boxes and Bandwidth Generously Provided by pair Networks
Perl Monk, Perl Meditation

comment on

( #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 ~/; echo $? Died at /home/me/ line 14 255 ## without local $@ me@myhost> perl ~/; 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

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

  • Posts are HTML formatted. Put <p> </p> tags around your paragraphs. Put <code> </code> tags around your code and data!
  • Titles consisting of a single word are discouraged, and in most cases are disallowed outright.
  • Read Where should I post X? if you're not absolutely sure you're posting in the right place.
  • Please read these before you post! —
  • Posts may use any of the Perl Monks Approved HTML tags:
    a, abbr, b, big, blockquote, br, caption, center, col, colgroup, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, ins, li, ol, p, pre, readmore, small, span, spoiler, strike, strong, sub, sup, table, tbody, td, tfoot, th, thead, tr, tt, u, ul, wbr
  • You may need to use entities for some characters, as follows. (Exception: Within code tags, you can put the characters literally.)
            For:     Use:
    & &amp;
    < &lt;
    > &gt;
    [ &#91;
    ] &#93;
  • Link using PerlMonks shortcuts! What shortcuts can I use for linking?
  • See Writeup Formatting Tips and other pages linked from there for more info.
  • Log In?

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

    How do I use this? | Other CB clients
    Other Users?
    Others meditating upon the Monastery: (4)
    As of 2021-04-12 07:31 GMT
    Find Nodes?
      Voting Booth?

      No recent polls found