views:

875

answers:

3

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?

+31  A: 

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 Ericson
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
+9  A: 

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
Yikes! I sincerely hope the package doesn't override the __DIE__ signal. Interesting idea, however. ;-)
Jon Ericson
Make those assignments to $SIG{__DIE__} local so you don't step on anyone else who did the same thing :)
brian d foy
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
+1 for thoroughness Axeman. And if I could downvote whoever thought $SIG{__DIE__} was a good idea (Larry?) I would.
j_random_hacker
It's incredible how many ways there are to cheat death in perl.
Ryan Thompson
+1  A: 

Although changing a die to not die has a specific solution as shown in the other answers, but 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 just 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.

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.

brian d foy
I believe that there are also modules on CPAN for *wrapping* an already-defined subroutine with your own code.
Ryan Thompson
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