Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister
 
PerlMonks  

Re: eval error stack (noise)

by tye (Sage)
on Nov 11, 2015 at 22:06 UTC ( [id://1147515]=note: print w/replies, xml ) Need Help??


in reply to eval error stack

my $bar = baz(); if ($@) { die "baz failed!\n"; }

Yeah, don't do that. That isn't a feature of Perl that is discarding the error reason. That is bad coding that is explicitly discarding the error reason. Such code should look at least more like:

my $bar = baz(); if ($@) { die "baz failed: $@\n"; } # ^^^^

The feature of (modern) Perl that could discard an error reason goes more like this:

#!/usr/bin/perl use strict; { package Deal; sub new { bless {} } sub make { "No collateral" } sub free_resources { unlink $_[0]->{tmp} } sub DESTROY { my( $self ) = @_; $self->free_resources() or die "Failed to free resources: $!\n"; } } eval { my $deal = Deal->new(); die "Can't make a deal: $_\n" for $deal->make(); 1 } or do { warn "Deal failed: $@\n"; settle(); }; sub settle { # ... }

Which produces:

Deal failed: Can't make a deal: No collateral

But all you have to do is add "-w" (such as to the "#!" line) to get:

Use of uninitialized value in unlink at - line 7. (in cleanup) Failed to free resources: No such file or directory Deal failed: Can't make a deal: No collateral

But, yes, you lose programmatic access to inner error(s).

But if you want programmatic access to errors, then you should be using structured exceptions and you get access to the inner error by having the outer exception store a reference to the inner exception.

If you ever use eval, when it fails, you need to do at least one of the following things with the resulting value of $@:

  1. Log it (such as via warn)
  2. Rethrow it (via die), usually decorated with more context info
  3. Precisely match it and handle it (and probably still log something)

Note that (3) is usually better done using structured exceptions or something like Errno.

We actually have a __DIE__ handler (at $work) in order to log why the program is about to exit (we don't just redirect STDERR straight to the log file as we prefer log lines to be in a parsable format and to include a precise timestamp and some context information). Unfortunately, our old version of Perl has a bug with $^S so we can't reliably distinguish between exceptions that are about to be caught from exceptions that are about to cause the program to exit.

So, for now, we have a flawed heuristic where exceptions that happen within a short time span of the program exiting are logged. I can tell you that it is easily very annoying to have caught exceptions being logged. Having a bunch of fatal-looking error messages that don't actually indicate any kind of problem sucks.

I don't see any way for your idea of collecting all exceptions to produce more signal than noise.

Actually, I don't even see how such an idea ever allows for an exception to be freed. So catching an exception just becomes a memory leak.

- tye        

Replies are listed 'Best First'.
Re^2: eval error stack (implementation dependent)
by shmem (Chancellor) on Nov 12, 2015 at 08:44 UTC
    Yeah, don't do that.

    Well, I certainly don't. This is a contrived example of course. Such code, if present, usually is buried inside a module chain (probably of third party modules), and if so, difficult to debug.

    But, yes, you lose programmatic access to inner error(s). But if you want programmatic access to errors, then you should be using structured exceptions and you get access to the inner error by having the outer exception store a reference to the inner exception.

    With @@, there would be no need to store references to the inner exception, and any code that doesn't, would do.

    I agree with all your points of proper error handling, and in fact I do follow them in my coding. But the code I write just makes up for 2% of the whole code perl exercises in any program or application running on my behalf.

    I don't see any way for your idea of collecting all exceptions to produce more signal than noise.

    This is the case also with e.g. Carp::confess and perls backtrace facility. It is in place, but you don't need it. Except when you do.

    Actually, I don't even see how such an idea ever allows for an exception to be freed. So catching an exception just becomes a memory leak.

    That depends on the semantics of @@. The current value of $@ could be pushed onto it, if $@ hasn't been accessed before resetting. And it could be handled via a pragma:

    use evalerrorstack; # @@ initialized ... no evalerrorstack; # @@ cleared

    But you didn't get my point, which is: $@ is related to eval errors. Why isn't @@ ? Probably the answer is: you don't need it, it can be done otherwise, and there is a canonical way to handle (eval) errors.

    Actually, the idea of using @@ comes from wanting to be able to say:

    sub expand { my $macros = shift; my $result; @{$result}{keys %$macros} = map { eval $_ } values %$macros; return if @@; $result; }

    If a subroutine fails, I usually set $@ and just return. In the above case, there may be a bunch of failures which are all related to macro expansion, which I would like to be able to report. The above is for now written as

    sub expand { my $macros = shift; my $result; my @errors; for ( keys %$macros ) { $result->{$_} = eval $macros->{$_}; push @errors, $@ if $@; } if ( @errors ) { $@ = join "\n", @errors; return; } $result; }

    but having @@ acummulate $@ would just be handy if failures occur in list context and/or $@ hasn't been accessed before it is being set again.

    perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'
      With @@, there would be no need to store references to the inner exception

      Of course there would be. Because I want the inner exception to be stored when it is actually part of the cause of the outer exception and not because it was an incidental exception that was thrown and handled inside of the code that later detected an error and threw the outer exception.

      The current value of $@ could be pushed onto it, if $@ hasn't been accessed before resetting.

      But your original example was, in part:

      if ($@) { die "baz failed!\n"; }

      The first line I quoted accessed $@. So your own solution doesn't work in your own example. So you have an implementation hair to split there.

      - tye        

        So you have an implementation hair to split there.

        ...since this whole meditation is about a hair split - well, then: evaluated but not accessed ? or perhaps checked for but not read ? But then, to check it, it has to be accessed, and to evaluate it, it must have been read. Too fine this hair to split for me is. Tough beans. This would mean adding new flags to $@ - accessed, checked, read, read & passed, perhaps even read & understood... ;-)

        Thanks for your time and patience.

        perl -le'print map{pack c,($-++?1:13)+ord}split//,ESEL'

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://1147515]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others scrutinizing the Monastery: (2)
As of 2024-04-20 03:52 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found