You are viewing a plain text version of this content. The canonical link for it is here.
Posted to modperl@perl.apache.org by Chris Nokleberg <ch...@sportsrocket.com> on 2000/10/20 01:41:31 UTC

Re: getting rid of nested sub lexical problem

Following up on my post on this subject a couple of months ago, here is a
proof-of-concept drop-in replacement for Apache::Registry that eliminates
the "my() Scoped Variable in Nested Subroutine" problem.

It requires PERL5OPT = "-d" and PERL5DB = "sub DB::DB {}" environment
variables set when starting the mod_perl-enabled httpd. This enables the
%DB::sub hash table that holds subroutine start and end line info. I
presume that this has some negative (marginal?) impact on performance. If
someone knows of a better way to reliably figure out where a subroutine
starts and ends, please let me know.

The processed code will probably generate harmless warnings if you have
them turned on.

I'm interested if there is support for turning this into a
production-quality module. Better yet, perhaps the existence of such an
ridiculous hack could spur the perl maintainers to provide a cleaner
solution.

--Chris

package My::CleanerRegistry;
use base Apache::RegistryNG;
use strict;

my %skip_subs = map { $_ => 1 } qw( handler BEGIN END );

sub compile {
    my ($pr, $eval) = @_;
    $eval ||= $pr->{'sub'};
    $pr->SUPER::compile($eval);
    my $package = quotemeta($pr->namespace);
    my @lines = split /\n/, $$eval;
    foreach my $sub (grep /^$package/, keys %DB::sub) {
        my ($name) = $sub =~ /:([^:]+)$/;
        next if $skip_subs{$name};
        my ($start, $end) = $DB::sub{$sub} =~ /:(\d+)-(\d+)$/;
        $start++, $end++;
        $lines[$start] =~ s:(sub[^\{]+\{):$1&\{sub\{:;
        $lines[$end]   =~ s:\}(?!.*\})$:\}\}\}:;
    }
    $$eval = join "\n", @lines;
    $pr->flush_namespace($package);
    my $rv = $pr->SUPER::compile($eval);
}

1;



On Wed, 16 Aug 2000 chris@sportsrocket.com wrote:

> Due to forgetfulness I was recently bitten by the infamous "my() Scoped
> Variable in Nested Subroutines" problem using Apache::Registry, and it got
> me thinking about whether it is fixable.
> 
> From the diagnostics:
>   This problem can usually be solved by making the inner subroutine
>   anonymous, using the sub {} syntax.  When inner anonymous subs that
>   reference variables in outer subroutines are called or referenced, they
>   are automatically rebound to the current values of such variables.
> 
> I think it should be possible for Registry to pre-process the source code
> to turn all top-level named subroutines into sub refs. For example,
> convert subroutines of the form
> 
>   sub foo { <CODE> }
> 
> into
> 
>   sub foo { &{ sub { <CODE> } } }
> 
> Are there cases for which this would not work? I have a sneaking suspicion
> that I am missing something important. If it is a reasonable solution, I
> imagine there are better ways to do this transformation than fancy
> regexps? Parse/modify/deparse?
> 
> Below is a processed version of the increment_counter example from the
> guide that works as expected.
> 
> --Chris
> 
>   #!/usr/bin/perl -w
>   use strict;
> 
>   for (1..3){
>       print "run: [time $_]\n";
>       run();
>   }
> 
>   sub run {
> 
>       my $counter = 0;
> 
>       increment_counter();
>       increment_counter();
> 
>       sub increment_counter {&{sub{
>           $counter++;
>           print "Counter is equal to $counter !\n";
>       }}}
> 
>   } # end of sub run
> 
> 


Re: getting rid of nested sub lexical problem

Posted by Chris Nokleberg <cj...@rock.pickem.com>.
On Thu, 21 Dec 2000, Doug MacEachern wrote:

> On Thu, 19 Oct 2000, Chris Nokleberg wrote:
> 
> > Following up on my post on this subject a couple of months ago, here is a
> > proof-of-concept drop-in replacement for Apache::Registry that eliminates
> > the "my() Scoped Variable in Nested Subroutine" problem.
> 
> nice hack!
>  
> > It requires PERL5OPT = "-d" and PERL5DB = "sub DB::DB {}" environment
> > variables set when starting the mod_perl-enabled httpd. This enables the
> > %DB::sub hash table that holds subroutine start and end line info. I
> > presume that this has some negative (marginal?) impact on performance. If
> > someone knows of a better way to reliably figure out where a subroutine
> > starts and ends, please let me know.
> 
> there is quite a bit of overhead when -d is enabled.  have a look at
> Apache::DB.xs, there's in init_debugger() function todo what -d does.  if
> another method was added to turn it off, after the registry script was
> compiled, the overhead would be reduced a great deal.

oooh, cool. I've added this to Apache::DB.xs:

int
stop_debugger()

    CODE:
    if (PL_perldb) {
        PL_perldb = 0;
        RETVAL = TRUE;
    }
    else
        RETVAL = FALSE;

    OUTPUT:
    RETVAL


It appears that setting PL_perldb to zero is all that's required to turn
off the debugger! I thought I might have to fiddle with the symbol tables
but this seems to work.

I've attached the latest version of Apache::NiceRegistry (suggestions
welcome) below. Not sure this is the right thing to do , but I call
Apache::DB::init_debugger directly because I need the return value (don't
want to stop_debugger if it was turned on outside of this handler), and I
don't want the warning that Apache::DB->init produces.

Also, why does Apache::DB unset $Apache::Registry::MarkLine?

Thanks,
Chris

--------------------------------------

package Apache::NiceRegistry;

use base Apache::RegistryNG;
use strict;
use Apache::DB ();

$Apache::Registry::MarkLine = 1;

my %skip_subs = map { $_ => 1 } qw( handler BEGIN END CHECK INIT );

sub compile {
    my ($pr, $eval) = @_;
    $eval ||= $pr->{'sub'};

    my $init_db = Apache::DB::init_debugger();
    $pr->SUPER::compile($eval);
    Apache::DB::stop_debugger() if $init_db;

    my $package = $pr->namespace;
    my @lines = split /\n/, $$eval;

    foreach my $sub (grep /^$\Qpackage\E/, keys %DB::sub) {
        my ($name) = $sub =~ /:([^:]+)$/;
        next if $skip_subs{$name};

        my ($start, $end) = $DB::sub{$sub} =~ /:(\d+)-(\d+)$/;
        $lines[$start + 1] =~ s:(sub[^\{]+\{):$1&\{sub\{:;
        $lines[$end + 1]   =~ s:\}(?!.*\})$:\}\}\}:;
    }

    $$eval = join "\n", @lines;
    $pr->flush_namespace($package);
    $pr->SUPER::compile($eval);
}

1;



Re: getting rid of nested sub lexical problem

Posted by Doug MacEachern <do...@covalent.net>.
On Thu, 19 Oct 2000, Chris Nokleberg wrote:

> Following up on my post on this subject a couple of months ago, here is a
> proof-of-concept drop-in replacement for Apache::Registry that eliminates
> the "my() Scoped Variable in Nested Subroutine" problem.

nice hack!
 
> It requires PERL5OPT = "-d" and PERL5DB = "sub DB::DB {}" environment
> variables set when starting the mod_perl-enabled httpd. This enables the
> %DB::sub hash table that holds subroutine start and end line info. I
> presume that this has some negative (marginal?) impact on performance. If
> someone knows of a better way to reliably figure out where a subroutine
> starts and ends, please let me know.

there is quite a bit of overhead when -d is enabled.  have a look at
Apache::DB.xs, there's in init_debugger() function todo what -d does.  if
another method was added to turn it off, after the registry script was
compiled, the overhead would be reduced a great deal.