Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Perl: $SIG{__DIE__}, eval { } and stack trace

I have a piece of Perl code somewhat like the following (strongly simplified): There are some levels of nested subroutine calls (actually, methods), and some of the inner ones do their own exception handling:

sub outer { middle() }

sub middle {
    eval { inner() };
    if ( my $x = $@ ) { # caught exception
        if (ref $x eq 'ARRAY') {
            print "we can handle this ...";
        }
        else {
            die $x; # rethrow
        }
    }
}

sub inner { die "OH NOES!" }

Now I want to change that code so that it does the following:

  • print a full stack trace for every exception that "bubbles up" all the way to the outermost level (sub outer). Specifically, the stack trace should not stop at the first level of "eval { }".

  • Not having to change the the implementation of any of the inner levels.

Right now, the way I do this is to install a localized __DIE__ handler inside the outer sub:

use Devel::StackTrace;

sub outer {
    local $SIG{__DIE__} = sub {
        my $error = shift;
        my $trace = Devel::StackTrace->new;
        print "Error: $error\n",
              "Stack Trace:\n",
              $trace->as_string;
    };
    middle();
}

[EDIT: I made a mistake, the code above actually doesn't work the way I want, it actually bypasses the exception handling of the middle sub. So I guess the question should really be: Is the behaviour I want even possible?]

This works perfectly, the only problem is that, if I understand the docs correctly, it relies on behaviour that is explicitly deprecated, namely the fact that __DIE__ handlers are triggered even for "die"s inside of "eval { }"s, which they really shouldn't. Both perlvar and perlsub state that this behaviour might be removed in future versions of Perl.

Is there another way I can achieve this without relying on deprecated behaviour, or is it save to rely on even if the docs say otherwise?

like image 650
trendels Avatar asked Jun 09 '09 16:06

trendels


1 Answers

UPDATE: I changed the code to override die globally so that exceptions from other packages can be caught as well.

Does the following do what you want?

#!/usr/bin/perl

use strict;
use warnings;

use Devel::StackTrace;

use ex::override GLOBAL_die => sub {
    local *__ANON__ = "custom_die";
    warn (
        'Error: ', @_, "\n",
        "Stack trace:\n",
        Devel::StackTrace->new(no_refs => 1)->as_string, "\n",
    );
    exit 1;
};

use M; # dummy module to functions dying in other modules

outer();

sub outer {
    middle( @_ );
    M::n(); # M::n dies
}

sub middle {
    eval { inner(@_) };
    if ( my $x = $@ ) { # caught exception
        if (ref $x eq 'ARRAY') {
            print "we can handle this ...";
        }
        else {
            die $x; # rethrow
        }
    }
}

sub inner { die "OH NOES!" }
like image 143
Sinan Ünür Avatar answered Oct 05 '22 12:10

Sinan Ünür