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

While playing around today with the combination of eval and require, I've discovered that require adds the modules it finds to %INC whether or not they can be fully compiled..

Since some modules don't tolerate repeated reloading and a broken module may compile partially (e.g. BEGIN blocks that make resource intensive DB connections), this makes perfect sense. We don't want to reload modules over and over, whether broken or not.

However, it has one not-so-nice side effect: I can't reliably use require to tell if a module is successfully loaded. My most common use of eval("require $module") is to dynamically include plugins. In general, I only want to enable functionality if the plug-in module compiles successfully.

All will be well and good if my call to eval("require $somePlugIn") just happens to be the first attempt to require this particular module and often it is. On the first attempt eval "require $somePlugIn" obligingly sets $@ to a list of compilation problems. However, successive calls only test to see if $somePlugIn is in %INC and, of course, return true.

The problem comes when a plug-in module is used in more than one place. Two scenarios come to mind: (a) two independently developed parts of an application rely on the same plugin or (b) the module is loaded via Test::More::require_ok and also in the modules being tested. The second scenario actually occurred earlier this afternoon and is the motivation for this question. A call to Test::More::require_ok gave me a false "ok" because it came after a use statement for a module that had also called require for the same plug-in.

When two different location in code, try to require the module, the first place to require it sees the error, but other places that try to require it dynamically via eval "require $somePlugIn ..." do not. They only see the true return value.

Now I could try to prevent problems by setting up my own special version of require. Before trying to load the module using CORE::require, it checks to see if the module is in the broken list and if so returns the original error rather than try to reload it. But this seems rather leaky to me: I can't guarantee that every module will use my special version of require: any module loaded before the module that contains the definition of the special require will use CORE::require, not my special function, and won't get added to my %BROKEN list.

I could also try social controls: A team decision that thou shalt use module Foo::Bar to load all plugins. But that only addresses code developed in house. It increases the likelihood that clients writing their own plug-ins will make a mistake that interferes with our ability to track broken modules. It limits our use of 3rd party modules since they don't necessarily play by our rules (e.g. Test::More). Even in-house social controls are not the perfect solution. Mistakes happen and tracking them down is time consuming. The "shape up or ship out" attitude isn't really the best approach either. Firing repeat offenders isn't really an option if they do a lot of other things "just right".

I am wondering if there is a non-hackish way to tell if a particular module in %INC compiled successfully. By non-hackish, I mean that:

I'm hoping someone will tell me that there is some sort of internal table I can get access to rather than maintaining my own %BROKEN table. Or that maybe someone who knows more than I can suggest a non-leaky way to wrap CORE::require so that I can keep my own table. Or even some totally different approach that makes this a non-problem.

Thanks in advance, beth

Replies are listed 'Best First'.
Re: Detecting broken modules
by moritz (Cardinal) on Jun 29, 2009 at 20:19 UTC
    Overriding require seems to work:
    # file TestModule.pm: package TestModule; die; 1; # file test.pl use strict; use warnings; use lib '.'; our %failed; BEGIN { *CORE::GLOBAL::require = sub { my $success = eval { CORE::require($_[0]) }; $failed{$_[0]} = 1 unless $success; return $success; }; } use TestModule; use CGI; use Data::Dumper; print Dumper \%failed;

    In this case use Module will never fail, which is good for testing but bad in general, so you might want to croak $@ unless $success after marking the module load as failed.

      Thanks for the example. I'm leaning in this direction if no general solution exists. My main problem with the override approach is that it only really works with certainty if you have one and only one entry point to your code (e.g. the test script). Otherwise, the overridden require only takes effect after the module defining it is loaded. Anything before that will be loaded by normal require and won't show up in the broken hash.

      Best, beth

Re: Detecting broken modules
by ikegami (Patriarch) on Jun 29, 2009 at 21:01 UTC

    If something attempted to load the module and the load failed, it doesn't make much sense to continue. Loading a module isn't transactional. At the very least, some cleanup is in order. Namely, the loader should clear the namespace and %INC entry.

      By loader do you mean the code that called eval require ... or Perl itself? If you mean Perl I would agree though I'm not sure how it would do it. As you say, loading isn't transactional. The partially compiled code may have successful BEGIN blocks that placed symbols in any number of namespaces, GLOBAL::CORE among them.

      If by "loader" you mean the code that called eval require ..., then the problems are even greater as regards clean-up. To begin with, the caller doesn't have the kind of knowledge that the compiler does about how much was actually compiled. The compiler could keep track of all symbols it has created/modified along with their pre-load values and restore them. This isn't really something the calling code could do.

      As for "doesn't make sense to continue"... that depends, at least from the caller's point of view. Indeed, it does not make sense to continue to do anything that uses the module's functionality - it is broken. However, if the module is an optional plug-in, then the brokenness may simply mean that one menu item is omitted or one item in a for-loop gets skipped with next.

      There is a second issue with removing the module name from %INC: as it stands now, having the module name placed in %INC has one valuable side effect. It makes it possible to tell the difference between a call to require that fails because the module doesn't exist at all and a require that fails because the module is broken. The missing module doesn't add anything to %INC and the failed one does. That can be useful when testing - we probably don't care if a require fails because an optional module is missing. In that case we want to skip any related tests. We probably do care if a require fails because an optional module is broken. In that case we want at least one failed test for the broken compilation plus several others marked as either fail or TODO.

      Without that distinction one would need to parse the exception itself - not terribly reliable when locales are taken into account. However, as discussed in the original post, that only happens on the first call to require. There after the broken and successful module both look like they succeeded.

      Best, beth

Re: Detecting broken modules
by moritz (Cardinal) on Jun 30, 2009 at 16:45 UTC
Re: Detecting broken modules
by Anonymous Monk on Jun 29, 2009 at 22:20 UTC
    adds the modules it finds to %INC whether or not they can be fully compiled..

    Prove it

      I knew someone would ask that question. :-)

      Sample broken module:

      use strict; use warnings; $x=1;

      Sample code using string eval (the above example from Anonymous dies before the print statement because it uses a constant module name within the non-string eval form and hence is fully evaluated at compile time.)

      my $sFile = 'Monks/Foo/Bar.pm'; my $result = eval("require Monks::Foo::Bar"); my $error = $@; print "=== Eval results ===\n"; print "result=<". (defined($result)?$result:'undef') .">\n\$@=<". (defined($error)?$error:'undef') .">\n"; print "=== Contents of %INC ===\n"; print "\$INC{$sFile}=<" . (defined($INC{$sFile}) ? $INC{$sFile} : 'undef') . ">\n";

      Which outputs

      === Eval results === result=<undef> $@=<Global symbol "$x" requires explicit package name at Monks/Foo/Bar +.pm line 3. Compilation failed in require at (eval 1) line 3. > === Contents of %INC === $INC{Monks/Foo/Bar.pm}=<Monks/Foo/Bar.pm>

      Q.E.D.

      Best, beth

      P.S. Using eval { $result=require($sFile); } yields the same output, so its not just the string version of eval.

      Update: scripts run on Perl 5.8.8

        What version of Perl are you using? I get the same results as you on an old box with 5.8.4 installed, but on my main box with 5.10.0, I get different results: $INC{module.pm} is undef if require module dies, and calling require module a second time fails.

        In both versions, I can trick the require into trying again, and producing the original error message for me, simply by wrapping my own call in a little localisation:

        { local %INC = %INC; delete $INC{$sFile}; $result = eval "require Monk::Foo::Bar"; $error = $@; }

        Though, on reflection, that has obvious issues for modules that really don't want to be loaded more than once, so maybe it's not a good idea.

        I'm not convinced :) but the argument could be made that strict/warnings is throwing an exception after module loaded OK
      BrokenModule.pm
      package BrokenModule; $BrokenModule::VERSION='BROKEN'; 0;
      test.pl
      #!/usr/bin/perl -- use strict; use warnings; eval q{ use BrokenModule; 1 } or warn $@; eval q{ use BrokenModule; 1 } or warn $@; eval { require BrokenModule; 1 } or warn $@; eval { require BrokenModule; 1 } or warn $@; print $_,$/ for grep /BrokenModule/, %INC; __END__ BrokenModule.pm did not return a true value at (eval 1) line 1. BEGIN failed--compilation aborted at (eval 1) line 1. BrokenModule.pm did not return a true value at (eval 2) line 1. BEGIN failed--compilation aborted at (eval 2) line 1. BrokenModule.pm did not return a true value at test.pl line 8. BrokenModule.pm did not return a true value at test.pl line 9.