Practical mod_perl, from O'Reilly. | 13
Practical mod_perl: Chapter 6: Coding with mod_perl in Mind
The First Mystery: Why Does the Script Go Beyond 5?
If we look at the error_log file (we did enable warnings), we'll see something like this:
Variable "$counter" will not stay sharedat /home/httpd/perl/counter.pl line 13.
This warning is generated when a script contains a named (as opposed
to an anonymous) nested subroutine that refers to a lexically scoped (with my(
)) variable defined outside this nested subroutine.
Do you see a nested named subroutine in our script? We don't! What's going on? Maybe it's a bug in Perl? But wait, maybe the Perl interpreter sees the script in a different way! Maybe the code goes through some changes before it actually gets executed? The easiest way to check what's actually happening is to run the script with a debugger.
Since we must debug the script when it's being executed by the
web server, a normal debugger won't help, because the debugger has to be invoked
from within the web server. Fortunately, we can use Doug MacEachern's Apache::DB
module to debug our script. While Apache::DB allows
us to debug the code interactively (as we will show in Chapter 21), we will
use it noninteractively in this example.
To enable the debugger, modify the httpd.conf file in the following way:
PerlSetEnv PERLDB_OPTS "NonStop=1 LineInfo=/tmp/db.out AutoTrace=1 frame=2"PerlModule Apache::DB<Location /perl>PerlFixupHandler Apache::DBSetHandler perl-scriptPerlHandler Apache::RegistryOptions ExecCGIPerlSendHeader On</Location>
We have added a debugger configuration setting using the PERLDB_OPTS
environment variable, which has the same effect as calling the debugger from
the command line. We have also loaded and enabled Apache::DB
as a PerlFixupHandler.
In addition, we'll load the Carp
module, using <Perl> sections (this could
also be done in the startup.pl file):
<Perl>use Carp;</Perl>
After applying the changes, we restart the server and issue a request to /perl/counter.pl, as before. On the surface, nothing has changed; we still see the same output as before. But two things have happened in the background:
- The file /tmp/db.out was written, with a complete trace of the code that was executed.
- Since we have loaded the
Carpmodule, the error_log file now contains the real code that was actually executed. This is produced as a side effect of reporting the "Variable "$counter" will not stay shared at..." warning that we saw earlier.
Here is the code that was actually executed:
package Apache::ROOT::perl::counter_2epl;use Apache qw(exit);sub handler {BEGIN {$^W = 1;};$^W = 1;use strict;print "Content-type: text/plain\n\n";my $counter = 0;for (1..5) {increment_counter( );}sub increment_counter {$counter++;print "Counter is equal to $counter !\n";}}
Note that the code in error_log wasn't
indented--we've indented it to make it obvious that the code was wrapped inside
the handler( ) subroutine.
From looking at this code, we learn that every Apache::Registry
script is cached under a package whose name is formed from the Apache::ROOT::
prefix and the script's URI (/perl/counter.pl) by
replacing all occurrences of / with ::
and . with _2e. That's
how mod_perl knows which script should be fetched from the cache on each request--each
script is transformed into a package with a unique name and with a single subroutine
named handler( ), which includes all the code that
was originally in the script.
Essentially, what's happened is that because increment_counter(
) is a subroutine that refers to a lexical variable defined outside of
its scope, it has become a closure. Closures don't
normally trigger warnings, but in this case we have a nested subroutine. That
means that the first time the enclosing subroutine handler(
) is called, both subroutines are referring to the same variable, but
after that, increment_counter( ) will keep its
own copy of $counter (which is why $counter
is not shared) and increment its own copy. Because
of this, the value of $counter keeps increasing
and is never reset to 0.
If we were to use the diagnostics
pragma in the script, which by default turns terse warnings into verbose warnings,
we would see a reference to an inner (nested) subroutine in the text of the
warning. By observing the code that gets executed, it is clear that increment_counter(
) is a named nested subroutine since it gets defined inside the handler(
) subroutine.
Any subroutine defined in the body of the script executed under
Apache::Registry becomes a nested subroutine. If
the code is placed into a library or a module that the script require(
)s or use( )s, this effect doesn't occur.
For example, if we move the code from the script into the subroutine
run( ), place the subroutines in the mylib.pl
file, save it in the same directory as the script itself, and require(
) it, there will be no problem at all.[1] Examples 6-1 and 6-2 show how
we spread the code across the two files.
Example 6-1: mylib.pl
my $counter;sub run {$counter = 0;for (1..5) {increment_counter( );}}sub increment_counter {$counter++;print "Counter is equal to $counter !\n";}1;
Example 6-2: counter.pl
use strict;require "./mylib.pl";print "Content-type: text/plain\n\n";run( );
This solution is the easiest and fastest way to solve the nested subroutine problem. All you have to do is to move the code into a separate file, by first wrapping the initial code into some function that you later call from the script, and keeping the lexically scoped variables that could cause the problem out of this function.
As a general rule, it's best to put all the code in external libraries
(unless the script is very short) and have only a few lines of code in the main
script. Usually the main script simply calls the main function in the library,
which is often called init( ) or run(
). This way, you don't have to worry about the effects of named nested
subroutines.
As we will show later in this chapter, however, this quick solution might be problematic on a different front. If you have many scripts, you might try to move more than one script's code into a file with a similar filename, like mylib.pl. A much cleaner solution would be to spend a little bit more time on the porting process and use a fully qualified package, as in Examples 6-3 and 6-4.
Created: March 27, 2003
Revised: July 23, 2003
URL: http://webreference.com/programming/perl/mod_perl/chap6/1

Find a programming school near you