ph713 has asked for the wisdom of the Perl Monks concerning the following question:

I spent a good deal of time today trying to wrap my tiny brain around a very unintuitive problem in my perl code. The project I'm working on is around 3.2 k lines of code, 100 kbytes in size, so it took a fair amount of time to track my way through the various modules using other modules, etc. I finally narrowed it into a simple testcase script, which I present here:

#!/usr/bin/perl -w use strict; use warnings; BEGIN { $SIG{__WARN__} = sub { print $_[0]; }; $SIG{__DIE__} = sub { print $_[0]; exit(99); }; } use Storable; print "This script sucks\n";

Being a contrived example, the reasons for doing things aren't clear, but that's not important right now. As I am beginning to understand (although I'm not quite there yet), my usage of a $SIG{__WARN__}/$SIG{__DIE__} handler like that inside of a BEGIN block is bad practice. In the original code of course it wasn't in a begin block, but rather it was in the body of a module the main script included, and as such is evaluated at BEGIN time. Apparently, I shouldn't be doing things like defining a global warn or die handler from inside a module, and there's other ways to work around that.

But that's not the point. The point is, if you execute the quoted little scriptlet on my machine, you get the following output:

Can't locate Log/Agent.pm in @INC (@INC contains: /etc/perl /usr/lib/p +erl5/site_perl/5.8.5/i686-linux-thread-multi /usr/lib/perl5/site_perl +/5.8.5 /usr/lib/perl5/site_perl /usr/lib/perl5/vendor_perl/5.8.5/i686 +-linux-thread-multi /usr/lib/perl5/vendor_perl/5.8.5 /usr/lib/perl5/v +endor_perl /usr/lib/perl5/5.8.5/i686-linux-thread-multi /usr/lib/perl +5/5.8.5 /usr/local/lib/site_perl .) at (eval 1) line 2.

Now, seeing as I've never heard of Log/Agent.pm, and I have no desire to have anyone logging anything for me, this little error was rather frightening. As it turns out, the Storable module included in the standard perl distribution optionally uses Log::Agent to help it give more informative warn/die messages, if Log::Agent happens to be installed (or at least, that's what I gather is going on).

Log::Agent is not in the standard distribution, and it's not normally an issue. I've used Storable all over the place in many projects, including this one, without the lack of Log::Agent ever becoming an issue. Only the above code snippet triggers this and seems to make Log::Agent a requirement. I have a feeling that Storable's use of Autoloader plays into this mess somewhere.

If you take the above script, and do any of the following three things to it, it suddenly doesn't have the "missing Log/Agent.pm" problem anymore:

Now whether my usage of the warn/die handlers was correct or not, this is just horrible buggy behavior in respones to it. However, I can't really track down the true nature of what's causing what to make this happen - I'd like to pin it down better and be able to submit a bug/patch to the Storable maintainers so that things work more cleanly and this issue either never happens, or at least gives a much more informative error that doesn't mention Log::Agent.

Thoughts? Anyone hit this mess before? ETA: Edited title later since apparently SIG{__DIE__} was the important part, not __WARN__

Replies are listed 'Best First'.
Re: SIG{__WARN__}, Storable, and Log::Agent...
by bmann (Priest) on May 06, 2005 at 20:49 UTC
    perldoc perlvar has this to say:

    Due to an implementation glitch, the $SIG{__DIE__} hook is called even inside an eval(). Do not use this to rewrite a pending exception in $@, or as a bizarre substitute for overriding CORE::GLOBAL::die(). This strange action at a distance may be fixed in a future release so that $SIG{__DIE__} is only called if your program is about to exit, as was the original intent. Any other use is deprecated.

    The key line is "even inside an eval()". Log::Agent is not found in the eval, and perl calls your DIE hook.

    Not knowing more about what you are trying to accomplish, I can only suggest moving the begin block after the use Storable; line.

      That explains a lot. "Strange action at a distance" is a lot more polite than what I would call it though. Storable is a pretty important core module, and the resulting error message makes absolutely no sense to the user.

      When perlvar says "Do not use this to ..., or as a bizarre substitute for overriding CORE::GLOBAL::die()", what exactly do they mean? I though that the entire purpose of $SIG{__DIE__} was exactly for overriding the global die() functionality.

      Perhaps my question should permute into this:

      I have a ton of code, much of it in modules, which in turn use other modules I've written, which in turn also use some 3rd party and/or core modules. When I execute the top level scripts that drag all of this in, I would like to enforce that all die()s and warn()s that happen, regardless of the module they occur in, call my special die/warn handlers to handle all of the error output in a consistent fashion.

      Since there are many top-level scripts, I would prefer to wrap the die/warn-handling code into another module, say named "MyProject::ErrorHandler", which I include in the top level scripts and/or the various modules I have control over. How does one go about accomplishing this correctly, without screwing up die()'s that occur in evals, which some modules (even my own) depend on.

        #!/usr/bin/perl -w use strict; use warnings; BEGIN { $SIG{__DIE__} = sub { die @_ if !defined($^S); die @_ if $^S; print "SIG{__DIE__} handling the following error: " . $_[0]; exit(99); }; } use Storable; print "This script sucks\n"; die "I will now die because I suck";
        ^^^ This seems to do what I want to do. perldoc -f die seems to think I should only need the "die @_ if $^S", but I've found the line before it with "die @_ if !defined($^S)" is also neccesary, at least in this example. According to perldoc perlvar:
         $^S     Current state of the interpreter.
        
                           $^S         State
                           ---------   -------------------
                           undef       Parsing module/eval
                           true (1)    Executing an eval
                           false (0)   Otherwise
        
                       The first state may happen in $SIG{__DIE__} and $SIG{__WARN__} handlers.
        
        In my case, it seems only the "0" case is what I want to be handling directly.
Re: SIG{__DIE__}, Storable, and Log::Agent...
by Anonymous Monk on May 07, 2005 at 13:44 UTC
    I'm using something like this:
    #!/usr/bin/perl -w use strict; use warnings; BEGIN { sub sigwarn { print "[MYHANDLER] ".$_[0]; } sub sigdie { print "[MYHANDLER] ".$_[0]; exit(99); } $SIG{__WARN__} = \&sigwarn; $SIG{__DIE__} = \&sigdie; } load_module('Storable'); print "This script sucks\n"; sub load_module { # eg: load_module('CGI',0,[qw(:all)]) my $class = shift || return; my $silent = shift || 0; my $import = shift || 0; # an arrayref if you have to import(PARAM) +, else: just a bool. (my $file = $class) =~ s[::][/]g; local $SIG{__DIE__}; eval {require "$file.pm"}; if ($@) { return if $silent; sigdie("Error loading module '$class': $@"); } # do import if $import return $class; }