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

15,047

Solution 1

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.

Solution 2

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.

Solution 3

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.

Share:
15,047
Johny Mahony
Author by

Johny Mahony

Research scientist finds himself dragged into software engineering-- at least it's interesting.

Updated on July 15, 2022

Comments

  • Johny Mahony
    Johny Mahony almost 2 years

    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?

  • Jon Ericson
    Jon Ericson over 15 years
    Yikes! I sincerely hope the package doesn't override the DIE signal. Interesting idea, however. ;-)
  • brian d foy
    brian d foy over 15 years
    Make those assignments to $SIG{DIE} local so you don't step on anyone else who did the same thing :)
  • Johny Mahony
    Johny Mahony over 15 years
    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!
  • Axeman
    Axeman over 15 years
    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.
  • Ankit Roy
    Ankit Roy over 15 years
    +1 for thoroughness Axeman. And if I could downvote whoever thought $SIG{DIE} was a good idea (Larry?) I would.
  • Ryan C. Thompson
    Ryan C. Thompson over 13 years
    It's incredible how many ways there are to cheat death in perl.
  • Ryan C. Thompson
    Ryan C. Thompson over 13 years
    I believe that there are also modules on CPAN for wrapping an already-defined subroutine with your own code.
  • brian d foy
    brian d foy over 13 years
    Yes, there are CPAN modules. Hook::LexWrap is one of them. That's one that I cover in the book.
  • Axeman
    Axeman about 13 years
    @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.
  • Ωmega
    Ωmega over 10 years
    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
    Ωmega over 10 years
    @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?
  • DVK
    DVK over 8 years
    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).
  • Aify
    Aify over 8 years
    @DVK Which DB access library are you using that dies on a connection error?
  • DVK
    DVK over 8 years
    @Aify - in-house proprietary library (on top of DBI)
  • choroba
    choroba over 8 years
    In pre 5.14 Perls, $@ can get clobbered. Using eval { ... ; 1 } or do { ... } is safer.