How can I get around a 'die' call in a Perl library I can't modify?
Asked Answered
H

3

36

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?

His answered 16/1, 2009 at 17:11 Comment(4)
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.Excavator
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.Thibaud
I agree with the commenters above meChessa
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.Lou
S
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.

Subtlety answered 16/1, 2009 at 17:13 Comment(6)
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!His
@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.Tatar
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).Audiogenic
@Audiogenic Which DB access library are you using that dies on a connection error?Entrain
@Entrain - in-house proprietary library (on top of DBI)Audiogenic
In pre 5.14 Perls, $@ can get clobbered. Using eval { ... ; 1 } or do { ... } is safer.Myrwyn
T
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.

Tatar answered 16/1, 2009 at 18:27 Comment(7)
Yikes! I sincerely hope the package doesn't override the DIE signal. Interesting idea, however. ;-)Subtlety
Make those assignments to $SIG{DIE} local so you don't step on anyone else who did the same thing :)Klemm
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.Tatar
+1 for thoroughness Axeman. And if I could downvote whoever thought $SIG{DIE} was a good idea (Larry?) I would.Syllabogram
It's incredible how many ways there are to cheat death in perl.Brynn
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..?Hocus
@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?Hocus
K
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.

Klemm answered 27/1, 2010 at 8:46 Comment(2)
I believe that there are also modules on CPAN for wrapping an already-defined subroutine with your own code.Brynn
Yes, there are CPAN modules. Hook::LexWrap is one of them. That's one that I cover in the book.Klemm

© 2022 - 2024 — McMap. All rights reserved.