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

puterboy has asked for the wisdom of the Perl Monks concerning the following question:

Unfortunately, one of the modules I am using (Weather::Com), liberally uses 'die' subroutines for non-fatal errors such as not being able to reach the weather.com server. This causes my whole Perl script to die, when at most I might want to warn and either ignore or try again.

So, without modifying the module itself, I would like to change the actions of all embedded 'die' commands to 'warn'.

The only way I can see to do that is to wrap each call to such a subroutine in Weather::Com as follows:
{ local $SIG{__DIE__} = sub { warn(@_); }; eval { weather_subroutine(); }; }
My questions are:
1. Is this the best/simplest way to do what I want?
2. Does wrapping in 'eval' (and changing $SIG{__DIE__} locally) have the potential to cause any additional and potentially undesirable side effects?

Replies are listed 'Best First'.
Re: Best way to replace 'die' with 'warn'
by dsheroh (Monsignor) on Dec 06, 2009 at 10:22 UTC
    The basic issue here, I think, is that, in Perl, "try" is spelled "eval" and "throw" is spelled "die". You've just run into the Perl exception-handling model and wrapping the call in eval is how this is intended to be handled.

    So, what to do about it?

    The most bare-bones way to deal with it would be

    eval { weather_subroutine(); } or do { warn $@; }; # <-- Note trailing semicolon; it *does* matter
    You could go even more minimal with
    eval { weather_subroutine(); } warn $@ if $@;
    but that has some weird edge cases where $@ can get messed up, so the eval { } or do { }; construct is more reliable.

    If you want something more robust and/or with more familiar spellings, take a look at TryCatch or Try::Tiny. (The Try::Tiny synopsis shows how to use it to turn an exception into a warning, so you could even lift that example straight out of the docs.)

      Thanks - the bare bones method looks nice and seems to do exactly what I need.
      OK. I just uncovered an "unintended" side effect when I tried to apply this technique in a converse fashion *another* script where instead of trying to prevent the script from exiting on "die", I only want to prepend an action to occur before exiting. Since this case in a sense the converse of the case in this thread, to avoid confusion, I am asking the full question in a new thread id=//811358.
Re: Best way to replace 'die' with 'warn' (no __DIE__)
by tye (Sage) on Dec 06, 2009 at 06:39 UTC

    Better:

    sub whether_subroutine() { my @results; if( ! eval { @results= weather_subroutine(@_); 1 } ) { warn $@; return; } return @results; }

    - tye        

Re: Best way to replace 'die' with 'warn'
by bv (Friar) on Dec 06, 2009 at 03:59 UTC

    Perhaps someone more knowledgeable could clarify, but perlmod seems to indicate that you can set the %SIG hash as a package variable, so you could do something like this:

    use Weather::Com; $Weather::Com::SIG{__DIE__} = sub { warn(@_) };

    I think that would cover every call to die() from within the Weather::Com package. Inspection of @_ within your new handler could let it die if something was really a big problem.


    @_=qw; Just another Perl hacker,; ;$_=q=print "@_"= and eval;
      That works nicely!

      Though I still have to wrap each function call with an eval...

      I still hope that a seasoned "monk" could verify that other than changing the behavior of die, the addition of the 'eval' command won't change the behavior of anything else. Or if it does, then help me understand what other side effects adding 'eval' could have
Re: Best way to replace 'die' with 'warn'
by Marshall (Canon) on Dec 06, 2009 at 04:50 UTC
    I haven't used the Weather module. Perl has a "redo" which allows re-starting of a while loop() without re-evaluating the stuff within the while(...). This is very useful for doing "retries". For example, this POST will fail about one out of every 5,000 times on my machine. I've never seen this happen 2x in a row unless the web server is "hard down".

    I think what you need is similar to this. Here I get a clear return value that says "It didn't work". Maybe you get something more indefinate like "I wanna die". If that is true, then use eval{} and plug the success or fail value into the "redo" code.

    my $max_retry =3; RETRY: while (my $n_attempt=0, $parameter=<>) { my $req = POST 'http:/...blah..../', [ action => '/db/', param => "$parameter", type => 'submit', value => 'Search', ]; my $res = $ua->request($req); # idea is to concentrate the "it didn't work" code to # right here unless ($res->is_success) ##1 of 5,000 case## { $n_attempt++; print STDERR "attempt # $n_attempt for $parameter\n"; print STDERR "$parameter ERROR: Try# $n_attempt of $max_retry: " +. $res->status_line . "\n"; sleep(1); redo RETRY if $n_attempt <= $max_retry; print STDERR "$parameter ERROR: Try# $n_attempt of ". "$max_retry FAILED: ". $res->status_line . "\n"; next; #we "Give up" at this point! And read next #parameter from <> } ### this is the "good machine case..." print $res->as_string if DEBUG_RAW; ......or whatever.... }