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

If a module returns false, then its filename is not added to %INC, which is supposed to mean that the module hasn't been loaded. But if it never returns anything, i.e. if there's a compile-time error or the module throws an exception, then the filename is added to %INC. As a result, if you try to load a module twice in a row, it can fail the first time and then look like it succeeds the second time, even though the file still doesn't really load successfully. Example:
foo.pl: for (1..2) { eval "require bar"; print "attempt $_: ", ($@ ? 'fail' : 'succeed'), "\n"; } bar.pm: die; > perl foo.pl attempt 1: fail attempt 2: succeed

Is this a perl bug, or is there some justification for the behavior? What's the correct way to load a module if and only if it hasn't already been loaded successfully?

Replies are listed 'Best First'.
Re: Failed require looks like it succeeded
by sh1tn (Priest) on Mar 21, 2005 at 01:24 UTC
    Is this a perl bug, or is there some justification for the behavior?

    perldoc -f require : ... demands that a library file be included if it hasn't already been included. The file is included via the do-FILE mechanism, which is essentially just a variety of "eval" ...

    The second time require does not have to include library (again) thus:
    #test.pl for (1..2) { eval "require bar"; print "attempt $_: ", ($@ ? "nok - $@" : "ok"), "\n"; } #bar.pm use Carp; croak "loaded$/" # STDOUT: attempt 1: nok - loaded require bar.pm called at (eval 1) line 3 eval 'require bar ;' called at test.pl line 3 Compilation failed in require at (eval 1) line 3. attempt 2: ok
    What's the correct way to load a module if and only if
    it hasn't already been loaded successfully?


    If a module cannot be loaded successfully the fisrt time why should we believe
    that it will be loaded successfully the second time?


      .. demands that a library file be included if it hasn't already been included. The file is included via the do-FILE mechanism, which is essentially just a variety of "eval"

      I can't find a definition of exactly what "included" means, but you seem to be saying that if the file is found, and is readable, and an attempt to compile and load it is made, then the file is considered to have been "included," even if the compilation or loading fails. But in fact this is not the case. Here's a pair of examples that shows the inconsistency:

      #foo.pl for (1..2) { eval "use bar"; print "attempt $_: ", ($@ ? 'fail' : 'succeed'), "\n"; } #bar.pm 0 #stdout attempt 1: fail attempt 2: fail # now change bar.pm to the following: die #stdout attempt 1: fail attempt 2: succeed

      In the first case, perl attempts to load bar.pm twice, but in the second case it only tries once. So apparently if a module loads successfully but returns 0, it has not been "included," but if it fails to load then it has been "included?"

      Incidentally, while preparing this example I discovered a falsehood in the documentation for "use." It says that use bar is exactly equivalent to BEGIN { require bar; import bar; } but if you substitute the latter for the former in the example, the results are different.

      FWIW, here's my situation. My application has a large number of modules, only a few of which are used by any given user. Currently they're all require'd at startup, with the require inside an eval so that if a module fails to load, the user is notified that it is unavailable, but he can continue to use the other modules. To reduce startup time, I want to postpone loading each module until the user explicitly activates it by clicking on it in the gui. If the user clicks on a module and it fails to load, he gets an error dialog. But if he's obstinate enough to click on it a second time, currently the application acts as if the module loaded successfully, even though it really didn't. I'm going to have to keep track explicitly of which modules the application has already attempted to load, and avoid trying any module twice. First of all, this is kind of annoying because I thought this was exactly the kind of thing that "require" was supposed to take care of. Secondly, it means that if the user fixes whatever condition caused the module not to load the first time (maybe he installed a dependency, fixed a configuration file, whatever... these modules are third-party plugins, out of my application's control) he's going to have to restart the application, which ought to be unnecessary.

      Thanks for your help.

        I can't find a definition of exactly what "included" means, but you seem to be saying that if the file is found, and is readable, and an attempt to compile and load it is made, then the file is considered to have been "included," even if the compilation or loading fails. But in fact this is not the case.
        No, he doesn't say that. You need to read perlfunc

        You could always delete the entry in INC so that the second require works as expected. Another solution would be to have modules register themselves and only try to load unregistered module. I've used both solutions in systems where modules might be created/fixed while the main app is running but I don't want to restart the main app.


        ___________
        Eric Hodges
        This may fix your immediate problem:
        # bar.pm BEGIN { eval { die } }
        or
        # bar.pm CHECK { die }
        Be well,
        rir
Re: Failed require looks like it succeeded
by blahblahblah (Priest) on Mar 21, 2005 at 03:03 UTC
    I think that loading the module with "do" instead of "require" will give you the control you're looking for. I've never tried it, but from "perldoc -f do" it looks pretty simple:
     do EXPR Uses the value of EXPR as a filename and executes the contents
             of the file as a Perl script. Its primary use is to include
             subroutines from a Perl subroutine library.
    
                 do 'stat.pl';
    
             is just like
    
                 eval `cat stat.pl`;
    
             except that it's more efficient and concise, keeps track of the
             current filename for error messages, searches the @INC
             libraries, and updates %INC if the file is found. See
             "Predefined Names" in perlvar for these variables. It also
             differs in that code evaluated with "do FILENAME" cannot see
             lexicals in the enclosing scope; "eval STRING" does. It's the
             same, however, in that it does reparse the file every time you
             call it, so you probably don't want to do this inside a loop.
    
             If "do" cannot read the file, it returns undef and sets $! to
             the error. If "do" can read the file but cannot compile it, it
             returns undef and sets an error message in $@. If the file is
             successfully compiled, "do" returns the value of the last
             expression evaluated.
    
             Note that inclusion of library modules is better done with the
             "use" and "require" operators, which also do automatic error
             checking and raise an exception if there's a problem.
    
             You might like to use "do" to read in a program configuration
             file. Manual error checking can be done this way:
    
                 # read in config files: system first, then user
                 for $file ("/share/prog/defaults.rc",
                            "$ENV{HOME}/.someprogrc")
                {
                     unless ($return = do $file) {
                         warn "couldn't parse $file: $@" if $@;
                         warn "couldn't do $file: $!"    unless defined $return;
                         warn "couldn't run $file"       unless $return;
                     }
                 }
    
    Just out of curiosity, why would the second attempt work? Are you modifying the file before retrying? Is it a configuration file or something?
Re: Failed require looks like it succeeded
by hv (Prior) on Mar 21, 2005 at 13:03 UTC

    This is a perl bug, already fixed in the current development track. In fact it was fixed early enough to get into the perl-5.9.1 interim release, so I'm not sure why it hasn't made it into a 5.8.x maintenance release - maybe the potential for backward compatibility problems seemed too high.

    Hugo