http://www.perlmonks.org?node_id=894432

John M. Dlugosz has asked for the wisdom of the Perl Monks concerning the following question:

I must be missing something... My Perl must be a tad rusty!

When I call send on a Mail::Builder::Simple object, the program dies and dumps a string with a string saying something to the effect of no SMTP server, and a trace dump. OK, now I know this function indicates failure by throwing an exception.

So I write:

my $mailer = new Mail::Builder::Simple::; eval { $mailer->send ( mail_client => { mailer => 'SMTP' }, to => 'john@dlugosz.com', from => $form->param_value('email'), subject => "[Contact Form]" . $form->param_value('subject'), plaintext => $form->param_value('message') ) }; if ($@) { my $err= $@; $c->log->info("what is this?" . ref($err)); $c->log->error("return value from email is:\n $err"); $c->stash->{form_error}= "$err"; } else { $c->stash->{form_sent}= 1; }
And I get, no matter how I look at it, an empty string for $@ that tests as true. Where did my huge error message go? If it's an object, I expect it to show a ref and stringify when interpolated.

Where did the error info go?

Replies are listed 'Best First'.
Re: Where did $@ go?
by Corion (Patriarch) on Mar 21, 2011 at 08:22 UTC

    The hip way of doing this is to use Try::Tiny. Personally, I prefer the following approach, to make sure the code actually died:

    my $ok = eval { $mailer->send(...); 1; }; if (! $ok) { my $err = $@; ... };

    But even my approach does not explain to me why if( $@ ) would be true but "$@" resp. "$err" would be empty.

      Tracing back through the calls, I found "Try::Tiny". It claims to localize $@. But if it does funny things, it means the caller must use Try::Tiny as well to get sensible results! Does every try-ing module create a different dependency on its users? That would be a nightmare! I guess it "restores" it after the non-existent catch!

      Using Try::Tiny, I get the thrown object in the catch block.

      Even data dumper shows, in my original call, $@ to be ''. But the code branch is executed, so it is true.

      As for your example, the core docs say that if eval completes without error, then $@ is guaranteed to be a null string, so you can write eval{...}; warn if $@;. You should not need to ensure your block evaluates to true, by original design of the feature.

        As for your example, the core docs say that if eval completes without error, then $@ is guaranteed to be a null string
        Even if that would be true, then
        so you can write eval{...}; warn if $@;
        doesn't follow.

        For the second, you also need $@ to be true if the eval fails. Which doesn't need to be the case.

        The problem is end-of-scope effects. eval { } is a block. Leaving the block, whether due to reaching the end, entering a return, or because the code dies, triggers end of scope effects. Which may cause more code to be executed (think DESTROY blocks). Which may clear, or set $@.

        This is a known, but hard problem to solve (as one doesn't want to lose information carried in $@). There will be improvements in 5.14, IIRC.

Re: Where did $@ go?
by ELISHEVA (Prior) on Mar 21, 2011 at 13:50 UTC

    an empty string for $@ that tests as true

    The last time I saw such weirdness (empty string tests as true), my so called empty string contained a null character as its first character. To confirm this print out $@ with Devel::Peek::Dump (see Devel::Peek).

    As for how that null got there? Anybody's guess. Exiting an eval block triggers the destructors for all data that is no longer needed. If there is an eval in one of those destructors, $@ can be easily reset. I didn't see any DESTROY subroutine in either Mail::Builder::Simple or Mail::Builder. However, Mail::Builder uses Moose which has plenty of magic and there are several other modules involved as well. Any one of them could be doing something that causes $@ to be reset.

    My own preferred syntax for exception handling is

    eval { ... my code here ... # eval is guaranteed to return undef if it fails # this ensures we only enter the do block (return false) # if and only if we die # Note: returns from eval NOT containing sub return 1; } or do { # save $@ because it is fragile and easily reset my $e=$@; ... my error handling code here ... };
Re: Where did $@ go?
by syphilis (Archbishop) on Mar 21, 2011 at 12:25 UTC
    And I get, no matter how I look at it, an empty string for $@ that tests as true

    The trueness just goes to prove that $@ is not an empty string ... and that further proves that you're not looking at (or at least seeing) $@ in the right way.

    Of course, that doesn't answer your question, but maybe it highlights what you need to focus on.
    Good luck with it - an intriguing situation :-)

    Cheers,
    Rob
Re: Where did $@ go?
by hpavc (Acolyte) on Mar 21, 2011 at 11:59 UTC
      The docs show an example of:
      print ref $@; # "Exception::Died"
      Indicating that it converts plain strings or whatever into objects. But I'm getting no string back from  ref $@ at all, not "Exception::Died" or any other class name.

      That is, I should still see something is in $@ and be able to figure out what to do with it using data dumper, ref, etc.

Re: Where did $@ go?
by ikegami (Patriarch) on Mar 21, 2011 at 15:37 UTC
    Care to give the output of
    use Devel::Peek; Dump($@);
      I commented out the try and put an eval back in, and after the call got:
      SV = PV(0x187eb88) at 0x1880ce8 REFCNT = 1 FLAGS = (ROK) RV = 0x55b67b0 SV = PVHV(0x55aa5f0) at 0x55b67b0 REFCNT = 1 FLAGS = (OBJECT,OVERLOAD,SHAREKEYS) STASH = 0x49f1540 "Email::Sender::Failure" ARRAY = 0x55dfed0 (0:3, 1:3, 2:2) hash quality = 111.4% KEYS = 7 FILL = 5 MAX = 7 RITER = -1 EITER = 0x0 Elt "stack_trace" HASH = 0x7848e70 SV = PVMG(0x56b3b30) at 0x56778c0 REFCNT = 1 FLAGS = (ROK) IV = 0 NV = 0 RV = 0x56779f8 SV = PVHV(0x55aa708) at 0x56779f8 REFCNT = 1 FLAGS = (OBJECT,OVERLOAD,SHAREKEYS) STASH = 0x567ad90 "Devel::StackTrace" ARRAY = 0x55dbba0 (0:5, 1:3) hash quality = 150.0% KEYS = 3 FILL = 3 MAX = 7 RITER = -1 EITER = 0x0 Elt "index" HASH = 0x91501139 SV = NULL(0x0) at 0x56779e0 REFCNT = 1 FLAGS = () PV = 0x56779f8 "" CUR = 0 LEN = 0 Elt "_recipients" HASH = 0x82243041 SV = RV(0x56b5af8) at 0x56b5ae8 REFCNT = 1 FLAGS = (ROK) RV = 0x56b5a58 SV = PVAV(0x5675d40) at 0x56b5a58 REFCNT = 1 FLAGS = () ARRAY = 0x258b8a0 FILL = 0 MAX = 0 ARYLEN = 0x0 FLAGS = (REAL) Elt No. 0 SV = PV(0x56ae658) at 0x56b5ab8 REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x56a4ff0 "sales@LEDsbythefoot.com"\0 CUR = 23 LEN = 24 Elt "stack_trace_class" HASH = 0x850b24fb SV = PV(0x527e468) at 0x5677638 REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x55dd2f0 "Devel::StackTrace"\0 CUR = 17 LEN = 24 PV = 0x55b67b0 "" CUR = 0 LEN = 0
      I changed it to Dump($@) if $@; to make sure it was still testing as true, and then got a shorter dump:
      SV = PV(0x187eb88) at 0x1880ce8 REFCNT = 1 FLAGS = (POK,pPOK) PV = 0x5592da0 ""\0 CUR = 0 LEN = 8
      So... testing it as a bool will destroy it! Am I reading that right?

        I'm not sure what I was hoping to find out, but I can tell you this: Email::Sender::Failure overloads stringification with fallback, which means the object gets stringified on testing it as a bool. $@ appears to be getting clobbered, which could happen in the stringification code.

        I'm pretty sure that only referencing $@ once will do the trick:

        if (my $e = $@) { ... use $e here ... }

        And if not, this should definitely do the trick:

        if (my $e = "$@") { ... use $e here ... }

        That should get the value the if was seeing.

        (You can move the assignment out of the "if" if you prefer.)