35

Yes, the problem is with a library I'm using, and no, I cannot modify it. I need a workaround.

Basically, I'm dealing with a badly written Perl library, that exits with 'die' when a certain error condition is encountered reading a file. I call this routine from a program which is looping through thousands of files, a handful of which are bad. Bad files happen; I just want my routine to log an error and move on.

IF I COULD modify the library, I would simply change the

die "error";

to a

print "error";return;

, but I cannot. Is there any way I can couch the routine so that the bad files won't crash the entire process?

FOLLOWUP QUESTION: Using an "eval" to couch the crash-prone call works nicely, but how do I set up handling for catch-able errors within that framework? To describe:

I have a subroutine that calls the library-which-crashes-sometimes many times. Rather than couch each call within this subroutine with an eval{}, I just allow it to die, and use an eval{} on the level that calls my subroutine:

my $status=eval{function($param);};
unless($status){print $@; next;}; # print error and go to next file if function() fails

However, there are error conditions that I can and do catch in function(). What is the most proper/elegant way to design the error-catching in the subroutine and the calling routine so that I get the correct behavior for both caught and uncaught errors?

Makoto
  • 104,088
  • 27
  • 192
  • 230
Ed Hyer
  • 1,385
  • 2
  • 13
  • 19
  • 2
    A die in the case of an error is not at all a sign for a library that is "badly written". Call it "throwing an exception" and suddenly is sounds much more advanced. – innaM Jan 16 '09 at 18:05
  • I agree with Manni on this one. Dying in response to errors, especially file related errors, means that it is more properly written than one that continues chugging along oblivious to those errors. – Mr. Muskrat Jan 16 '09 at 19:05
  • I agree with the commenters above me – Leon Timmermans Jan 17 '09 at 01:13
  • 1
    While it's good that the library is doing error checking, it's bad that it has decided that errors should be fatal. In most cases the function(s) should probably do a bare 'return' instead. – Michael Carman Jan 22 '09 at 01:19

3 Answers3

70

You could wrap it in an eval. See:

perldoc -f eval

For instance, you could write:

# warn if routine calls die
eval { routine_might_die }; warn $@ if $@;

This will turn the fatal error into a warning, which is more or less what you suggested. If die is called, $@ contains the string passed to it.

Jon 'links in bio' Ericson
  • 20,880
  • 12
  • 98
  • 148
  • 2
    Two-line solution FTW! Had that coded and running in ten minutes flat, and it does the job nicely. Now that they don't derail my whole processing chain, I now have stats on the frequency of busted files too! – Ed Hyer Jan 16 '09 at 21:29
  • 3
    @Ed Hyer The steps in my solution are a more general way. If somebody has trapped `$SIG{__DIE__}` a simple `eval` won't work, because the DIE handler will be invoked first. – Axeman Feb 03 '11 at 15:33
  • Fair warning: this may not always work to your ultimate satisfaction. As a random example, our DB access library dies on DB connection error. However, trapping that die in an `eval {}` block is useless for any complicated SQL involving # temp tables, because the underlying library **severes the connection** when dying, thus dropping all the # temp tables before your code gets back control from `eval` - frequently rendering your caller code unable to do anything more meaningful than prettied-up error handling (e.g. no retries). – DVK Dec 14 '15 at 17:44
  • @DVK Which DB access library are you using that dies on a connection error? – Aify Dec 15 '15 at 02:10
  • @Aify - in-house proprietary library (on top of DBI) – DVK Dec 15 '15 at 04:03
  • In pre 5.14 Perls, `$@` can get clobbered. Using `eval { ... ; 1 } or do { ... }` is safer. – choroba Jan 23 '16 at 15:31
28

Does it trap $SIG{__DIE__}? If it does, then it's more local than you are. But there are a couple strategies:

  • You can evoke its package and override die:

    package Library::Dumb::Dyer;
    use subs 'die';
    sub die {
        my ( $package, $file, $line ) = caller();
        unless ( $decider->decide( $file, $package, $line ) eq 'DUMB' ) {
            say "It's a good death.";
            die @_;
       }
    } 
    
  • If not, can trap it. (look for $SIG on the page, markdown is not handling the full link.)

    my $old_die_handler = $SIG{__DIE__};
    sub _death_handler { 
        my ( $package, $file, $line ) = caller();
        unless ( $decider->decide( $file, $package, $line ) eq 'DUMB DIE' ) {
            say "It's a good death.";
            goto &$old_die_handler;
        }
    }
    $SIG{__DIE__} = \&_death_handler;
    
  • You might have to scan the library, find a sub that it always calls, and use that to load your $SIG handler by overriding that.

    my $dumb_package_do_something_dumb = \&Dumb::do_something_dumb;
    *Dumb::do_something_dumb = sub { 
        $SIG{__DIE__} = ...
        goto &$dumb_package_do_something_dumb;
    };
    
  • Or override a builtin that it always calls...

    package Dumb; 
    use subs 'chdir';
    sub chdir { 
        $SIG{__DIE__} = ...
        CORE::chdir @_;
    };
    
  • If all else fails, you can whip the horse's eyes with this:

    package CORE::GLOBAL;
    use subs 'die';
    
    sub die { 
        ... 
        CORE::die @_;
    }
    

This will override die globally, the only way you can get back die is to address it as CORE::die.

Some combination of this will work.

Axeman
  • 29,660
  • 2
  • 47
  • 102
  • Yikes! I sincerely hope the package doesn't override the __DIE__ signal. Interesting idea, however. ;-) – Jon 'links in bio' Ericson Jan 16 '09 at 18:36
  • Make those assignments to $SIG{__DIE__} local so you don't step on anyone else who did the same thing :) – brian d foy Jan 16 '09 at 20:27
  • You know, I thought about that. But I wasn't aware of the calling structure. Now, I realize that my whole approach was overblown if all he wanted was to "try" the library and continue on. So the library probably isn't so dumb. It just expects you "catch" to catch it. – Axeman Jan 16 '09 at 23:10
  • 1
    +1 for thoroughness Axeman. And if I could downvote whoever thought $SIG{__DIE__} was a good idea (Larry?) I would. – j_random_hacker Jan 17 '09 at 08:50
  • 5
    It's incredible how many ways there are to cheat death in perl. – Ryan C. Thompson Aug 29 '10 at 08:11
  • Is `$SIG{__DIE__} = \&_death_handler;` strategy safe even for possible event of crash inside of this die handler? What will happen if something goes wrong inside of the handler? Wouldn't it lead into catastrophic infinity recursive calling..? – Ωmega Dec 12 '13 at 13:32
  • @briandfoy - What you mean by *"Make those assignments to `$SIG{DIE}` local"*..? Can you please clarify what needs to be changed in the code to make it local? – Ωmega Dec 12 '13 at 13:36
8

Although changing a die to not die has a specific solution as shown in the other answers, in general you can always override subroutines in other packages. You don't change the original source at all.

First, load the original package so you get all of the original definitions. Once the original is in place, you can redefine the troublesome subroutine:

 BEGIN {
      use Original::Lib;

      no warnings 'redefine';

      sub Original::Lib::some_sub { ... }
      }

You can even cut and paste the original definition and tweak what you need. It's not a great solution, but if you can't change the original source (or want to try something before you change the original), it can work.

Besides that, you can copy the original source file into a separate directory for your application. Since you control that directory, you can edit the files in it. You modify that copy and load it by adding that directory to Perl's module search path:

use lib qw(/that/new/directory);
use Original::Lib;  # should find the one in /that/new/directory

Your copy sticks around even if someone updates the original module (although you might have to merge changes).

I talk about this quite a bit in Mastering Perl, where I show some other techniques to do that sort of thing. The trick is to not break things even more. How you not break things depends on what you are doing.

brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • I believe that there are also modules on CPAN for *wrapping* an already-defined subroutine with your own code. – Ryan C. Thompson Aug 29 '10 at 08:12
  • Yes, there are CPAN modules. [Hook::LexWrap](http://search.cpan.org/dist/Hook-LexWrap) is one of them. That's one that I cover in the book. – brian d foy Aug 30 '10 at 21:56