Override die with END or CORE::GLOBAL::die

Perl lets you override the effects of warn and die by redefining the signals that Perl sends when you call those functions. You probably don’t want to use the signal from die, though, since it might mean a couple of different things.

You handle these special signals by setting values in the %SIG hash just like you would for any other signal. Since these are internal to perl, the signal names have underscores around them to distinguish them from external signal names:

$SIG{__WARN__} = \&some_Sub;
$SIG{__DIE__} = sub { ... };

I’ll treat the __WARN__ in a different Item, because even though these look like they should do the same sort of thing, they really don’t. The warn prints a message and lets the program continue. The die is designed to kill the program (hence, it’s morbid name) by starting a sequence of events that leads to the program’s shutdown. However, die became Perl’s fake exception mechanism, now used more to signal an error but without the intent to stop the program. Most of what die is supposed to do is discarded by catching it in an eval now:

eval {
	...;
	die( ... );
	...;
	} or do { ... };

This still triggers the __DIE__ signal even though Perl knows that you aren’t going to exit the program. This means that if you want to handle __DIE__ on your own, you need to distinguish between those cases where you should shut down the program, which seem to be the rare uses now, and the ones inside an eval that should not shutdown the program. You don’t want to, for instance, start cleaning up database connections, logging, and other resources if the program is going to magically recover saying “I’m not dead yet!”.

There’s a Perl special variable that can help. The documentation for die recommends that you start your __DIE__ handler by checking $^S before you continue. Inside the handler, the special handler is turned off so die inside $SIG{__DIE__} is the real die. The value of $^S will be true if it came from within an eval, so this just re-throws the exception without handling it:

$SIG{__DIE__} = sub {
	die @_ if $^S;
	...;
	};

This way, you’ll skip anything you might want to do to handle during a real shutdown.

There’s an additional twist. Perl might throw the __DIE__ signal if there’s a problem during parsing. That is, you can actaully catch compilation errors:

use strict;
use warnings;
use 5.010;

BEGIN { $SIG{__DIE__} = sub { print "Caught error: [@_]\n" } }

while( <> ) {
	next if m/(/; # compile-time error
	}

Now you get a double error message. You might not have meant to catch those sorts of errors, but you might.

Caught error: [Unmatched ( in regex; marked by <-- HERE in m/( <-- HERE / at test line 8.
]
Unmatched ( in regex; marked by <-- HERE in m/( <-- HERE / at test line 8.

The problem with these errors is that the parser might not have done everything you thought it should have done by tht point. Relying on anything, such as a subroutine definition is dangerous. The value of $^S will be undefined in this case, so your handler might now become:

$SIG{__DIE__} = sub {
	die @_ if( $^S or not defined $^S );
	...;
	};

Skipping those two cases, the eval and compile-time parsing, leaves you just with the case of actually exiting the program. However, there's another mechanism that might work better for you: the END block, which I covered in Know the phases of a Perl program�s execution. Anything in the END block executes as the program shuts down:

END {
	...
	}

However, if you really need to catch calls to die, you can define CORE::GLOBAL::die (see Use CORE when you need the real thing). This won't catch parsing errors, and will only affect calls to die (even in an eval). To use it, you need to declare it before perl parses any of the die statements that you want to replace. You can do this in a BEGIN block:

use strict;
use warnings;
use 5.010;

BEGIN { *CORE::GLOBAL::die = sub { say "In CORE::GLOBAL::die: [@_]" } }

eval {
	say "Inside eval";
	die 'Die-ing in eval';
	say "Past die in eval";
	};

die "This is the real die";

say "I got to the end!";

This version of die merely outputs a message, but without stopping the program. Thus, you see the message from the last say in the eval:

Inside eval
In CORE::GLOBAL::die: [Die-ing in eval]
Past die in eval
In CORE::GLOBAL::die: [This is the real die]
I got to the end!

Your new die doesn't stop the program, so you might put in an exit:

use strict;
use warnings;
use 5.010;

BEGIN {
*CORE::GLOBAL::die = sub {
	say "In CORE::GLOBAL::die: [@_]";
	exit(1);
	}
	}

eval {
	say "Inside eval";
	die 'Die-ing in eval';
	say "Past die in eval";
	} or do {;
	    say "Caught [$@]";
	    }

die "This is the real die";

say "I got to the end!";

Now your program stops in the eval because you call exit, but notice that it actually exits. That means there is no $@ to catch. eval does not catch an exit:

Inside eval
In CORE::GLOBAL::die: [Die-ing in eval]

If you want to get back to the real die, you can use CORE::die. For instance, you can die only if you are in an eval, making it catchable, and exit otherwise:

use strict;
use warnings;
use 5.010;

BEGIN {
*CORE::GLOBAL::die = sub {
	say "In CORE::GLOBAL::die: [@_]";
	CORE::die( @_ ) if $^S;
	exit(1);
	}
	}

eval {
	say "Inside eval";
	die 'Die-ing in eval';
	say "Past die in eval";
	} or do {
	    say "Caught: [$@]";
	}

die "This is the real die";

say "I got to the end!";

Now the program behavior is almost back to what you expect from the real die:

Inside eval
In CORE::GLOBAL::die: [Die-ing in eval]
Caught: [Die-ing in eval at test line 8.
]
In CORE::GLOBAL::die: [This is the real die]

Now that you are this far, there are a few more tricks that you can play. For instance, you might want to catch all string arguments to die and turn them into objects. If they are already objects, you propagate them with some extra information.

Here's an example with a MyError class you can use as an error object. It knows how to stringify its object and it defines its own special PROPAGATE method to carry a message through several layers of eval:

use strict;
use warnings;
use Scalar::Util qw(blessed);
use 5.014;

package MyError {
	use overload '""' => \&as_string;

	sub new {
		bless { message => $_[1] }, $_[0];
		}

	sub PROPAGATE {
		$_[0]->{message} .= "Passed through at file $_[1] line $_[2]\n";
		}

	sub as_string { "Stringified: <$_[0]->{message}>" }
	}

BEGIN {
*CORE::GLOBAL::die = sub {
	say "In CORE::GLOBAL::die: [@_]";
	$@ = do {
		if( blessed $@ and $@->can('PROPAGATE') ) {
			$@->PROPAGATE(__FILE__, __LINE__);
			}
		elsif( not blessed $@ ) {
			MyError->new( 'Made object: [' . join( ' ', @_) . "]\n" );
			}
		};
	CORE::die( $@ );
	}
	}

eval {
	eval {
		say "Inside eval";
		die 'Die-ing in eval';
		say "Past die in eval";
		} or do {
		    die $@;
		}
	} or do {
	    say "Caught: [$@]";
	}

die "This is the real die";

say "I got to the end!";

Now the output is a layered mess of error messages within error messages, but you had quite a bit of control over those messages:

Inside eval
In CORE::GLOBAL::die: [Die-ing in eval]
In CORE::GLOBAL::die: [Stringified: <Made object: [Die-ing in eval]
>]
In CORE::GLOBAL::die: [Caught: [Made object: [Die-ing in eval]
Passed through at file test line 25
]]
Stringified: <Made object: [Caught: [Made object: [Die-ing in eval]
Passed through at file test line 25
]]
>

Things to remember

  • $SIG{__DIE__} catches much more than a program's death
  • Use END blocks for end-of-program cleanups if you can
  • Override CORE::GLOBAL::die if you really need to replace die.

Post to Twitter Post to Delicious Post to Digg Post to Facebook Post to Reddit

Leave a comment

0 Comments.

Leave a Reply

You must be logged in to post a comment.