Beefy Boxes and Bandwidth Generously Provided by pair Networks
Pathologically Eclectic Rubbish Lister

require in script breaks module

by chrstphrchvz (Scribe)
on Mar 02, 2019 at 16:33 UTC ( #1230761=perlquestion: print w/replies, xml ) Need Help??

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

Howdy Monks,

I have a test that I would like to skip if a requisite .ph file is not available, so I considered using:

eval { require 'sys/' }; # skip if $@ contains # "Can't locate 'sys/' in @INC (did you run h2ph?)"

However, doing require 'sys/' from the test script "breaks" the module.

Here is a simplified equivalent:

package funnybusiness; use warnings; use strict; sub s1 { require 'sys/'; print FIONREAD() . "\n"; # should print some number } 1;

use warnings; use strict; use funnybusiness; #require 'sys/'; funnybusiness::s1;

Assuming sys/ is available, running prints the value of FIONREAD in sys/ioctl.h. But uncommenting the require statement in causes an Undefined subroutine &funnybusiness::FIONREAD error.

I can prevent the error by having use main::FIONREAD() instead (edit: that breaks normal code that doesn't have the require statement). But I would like to understand why the require statement in the script is "breaking" the module: whether there's some bad assumption that makes about what FIONREAD() can mean, or if the script should somehow know better than to do a require that might be done by the module, or if this behavior might be a bug/limitation/feature, etc.

(Actual module for anyone curious:

The module makes use of AUTOLOAD, meaning I get something more wacky than a Undefined subroutine error.)

Replies are listed 'Best First'.
Re: require in script breaks module
by haukex (Archbishop) on Mar 02, 2019 at 16:58 UTC

    What's going on is that the sys/ file (at least the one on my system) defines a bunch of subs, however, it does not use something like Exporter to do so, it just defines them in whatever the current package happens to be. Also, require only loads a file once: if you have require 'sys/' in your package main (i.e. that gets called first, then the require 'sys/' in package funnybusiness has no effect, and therefore, the sub funnybusiness::FIONREAD never gets defined.

    There are several possible solutions, which one is most "elegant" depends... One possible solution might be to use do instead of require, because it always runs the file, no matter how many times it's been run before, but keeping in mind the error checking described in the documentation of do. Also, I'd move that do outside of the sub s1, there's no need for the file to get loaded every time the sub is called. As an alternative to do, one could do delete $INC{'sys/'}; require 'sys/'; to make sure the require loads the file (see %INC).

    Yet another solution might be to do the following in one of the library files:

    { package Sys::Ioctl; BEGIN { delete $INC{'sys/'}; require 'sys/'; } }

    And then to use Sys::Ioctl::FIONREAD() everywhere. This could also be separated out into its own Sys/ file, which could in turn use Exporter to get those definitions exported in the usual manner. This is probably the solution I would favor in a larger application.

      Thanks for the reply and suggesting possible workarounds.

      Peeking inside the .ph file on my machines, it's mostly just more requires. So unfortunately neither do 'sys/' nor delete $INC{'sys/'}; require 'sys/' alone yield any improvement.

      I ended up moving the require to where it would be run once during the module's import, only nested in an eval so that any error is saved to a package variable. Then, when the feature requiring ioctl is used, any error will be croaked; and tests can check the package variable to know whether to skip. Here's the implementation:

      Maybe there is something more elaborate that resists the effects of require appearing in "user" code, but because this particular feature in the module is due for replacement (likely by something written in C/XS), I've opted for something low-effort and less invasive as it were.

        Peeking inside the .ph file on my machines, it's mostly just more requires.

        Yes, the "load a file only once" logic with %INC would apply to every require call. I don't have enough time to test right now, but assuming that those files don't load any other modules, an ugly hack might be { package Foo; local %INC; require "..."; }

        By the way, I took a look at your commit, and I noticed this logic:

        eval { require 'sys/' }; $Tcl::pTk::_FE_unavailable = $@; ... if ($Tcl::pTk::_FE_unavailable) { ...

        Be aware of Bug in eval in pre-5.14 and that $@ may not be a true value after an eval failure. The better pattern would be for example:

        eval { require 'sys/'; 1 } or do { $Tcl::pTk::_FE_unavailable = $@||'unknown error'; };

        because in this case $Tcl::pTk::_FE_unavailable will always be a true value if an error occurs, even if $@ is not.

Re: require in script breaks module
by hippo (Bishop) on Mar 02, 2019 at 16:56 UTC
    But I would like to understand why the require statement in the script is "breaking" the module

    My guess (and it is just a guess) is that when the require happens in the script, it is before the require in the module (since require only happens at run-time). The require in the module then is a no-op because the target has already been loaded once. But the scope of the exported FIONREAD() function is in the script now and not the module, so the call to the sub in the module fails.

    Does that make sense? You could probably confirm this with Devel::Trace or similar.

Log In?

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perlquestion [id://1230761]
Approved by johngg
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others having an uproarious good time at the Monastery: (3)
As of 2022-09-27 07:42 GMT
Find Nodes?
    Voting Booth?
    I prefer my indexes to start at:

    Results (118 votes). Check out past polls.