Beefy Boxes and Bandwidth Generously Provided by pair Networks
We don't bite newbies here... much
 
PerlMonks  

mod_perl / Apache::Registry accidental closures

by imp (Priest)
on Jul 21, 2006 at 00:45 UTC ( [id://562746]=perltutorial: print w/replies, xml ) Need Help??

When working with Apache::Registry it is very easy to create accidental closures.
This is due to the way Apache::Registry makes a fake package for your script, as I'll demonstrate in this tutorial.

The only indication that something is amiss (other than the unpredictable behaviour) will be the following line in your error log: Variable "$foo" will not stay shared at ...

The following code demonstrates the problem with accidental closures when using Apache::Registry.

use strict; use warnings; my $foo = 5; print "Content-type: text/plain\n"; print "Content-disposition: inline; filename=foo.txt\n\n"; printf "Package: %s\n", __PACKAGE__; printf "[%s] Before: %s\n", $$, $foo; badness(5); printf "[%s] After: %s\n", $$, $foo; sub badness { my $val = shift; printf "[%s] badness: %s\n", $$, $foo; $foo += $val; }

Apache::Registry will take the above code and create a new package for it based on the ServerName and the name of the script, and then wrap the code in a sub handler {} block.

If your script is running on "foo.com" and is named "test.pl", then this is what the above code will look like after Apache::Registry is done:

package Apache::ROOTfoo_2ecom::test_2epl; use Apache qw(exit); sub handler { #line 1 /www/foo.com/test.pl use strict; use warnings; my $foo = 5; print "Content-type: text/plain\n"; print "Content-disposition: inline; filename=foo.txt\n\n"; printf "Package: %s\n", __PACKAGE__; printf "[%s] Before: %s\n", $$, $foo; badness(5); printf "[%s] After: %s\n", $$, $foo; sub badness { my $val = shift; printf "[%s] badness: %s\n", $$, $foo; $foo += $val; } }
First run:
Package: Apache::ROOTfoo_2ecom::test_2epl [13520] Before: 5 [13520] badness: 5 [13520] After: 10
Second:
Package: Apache::ROOTfoo_2ecom::test_2epl [19331] Before: 5 [19331] badness: 5 [19331] After: 10
Third:
Package: Apache::ROOTfoo_2ecom::test_2epl [19331] Before: 5 [19331] badness: 10 [19331] After: 5
Fourth:
Package: Apache::ROOTfoo_2ecom::test_2epl [19331] Before: 5 [19331] badness: 15 [19331] After: 5
Notice how the number within the badness sub is increasing for each process, but the $foo that is seen by the instance script is never modified after 'badness' after the first execution for that process.

This is because the badness function is actually an inner function now, and it keeps a reference to the instance of $foo that was created for the first run.

Edit - example of how to avoid this issue added, per rhesa's suggestion

Thankfully it is easy to avoid these problems once you know why they occur.
Tips:

  • Keep your toplevel script minimal
  • Subroutines should only use the variables that were passed
  • Encapsulate behaviour in supporting objects
Example of a working alternative:
use strict; use warnings; my $foo = 5; print "Content-type: text/plain\n"; print "Content-disposition: inline; filename=foo.txt\n\n"; printf "Package: %s\n", __PACKAGE__; printf "[%s] Before: %s\n", $$, $foo; badness(\$foo, 5); badness(\$foo, 5); printf "[%s] After: %s\n", $$, $foo; sub badness { my ($foo,$val) = @_; printf "[%s] badness: %s\n", $$, $$foo; $$foo += $val; }

Replies are listed 'Best First'.
Re: mod_perl / Apache::Registry accidental closures
by rhesa (Vicar) on Jul 21, 2006 at 01:43 UTC
    Good explanation. I'd suggest adding something that explicitly explains how to avoid this problem.

    The basic solution is to wrap all top-level code that isn't inside a subroutine in a new sub, and then fixing all the problems with global variables.

    In this case, something like:

    use strict; use warnings; main(); # <----- change + here sub main { # <----- change + here my $foo = 5; print "Content-type: text/plain\n"; print "Content-disposition: inline; filename=foo.txt\n\n"; printf "Package: %s\n", __PACKAGE__; printf "[%s] Before: %s\n", $$, $foo; badness(5); printf "[%s] After: %s\n", $$, $foo; } # <----- change + here sub badness { my $val = shift; printf "[%s] badness: %s\n", $$, $foo; $foo += $val; }

    That will not run, so we need to fix the reference to $foo in badness:

    use strict; use warnings; main(); main(); main(); sub main { my $foo = 5; print "Content-type: text/plain\n"; print "Content-disposition: inline; filename=foo.txt\n\n"; printf "Package: %s\n", __PACKAGE__; printf "[%s] Before: %s\n", $$, $foo; badness(5, $foo); # <----- change + here printf "[%s] After: %s\n", $$, $foo; } sub badness { my $val = shift; our $foo; # <----- clever + trick here(*) local *foo = \shift; # <- printf "[%s] badness: %s\n", $$, $foo; $foo += $val; }

    Real-life scripts will probably be harder, so make sure to run perl -c your_script.pl early and often :^)

    Update: As ikegami pointed out, using my $foo = shift in badness runs, but it doesn't act the same. The (*)clever trick suggested by ikegami is a good way to circumvent that. Later iterations should probably move to a more functional approach, by making the subroutines free of side effects.

      The easiest solution by far is to just pass all the required variables to your subs. This is a good programming practice anyway.
        I agree - that's the only change I made to the alternative that I recommended at the end of the post.

        In that case I passed the values by reference, to preserve the behaviour of the original function (which modified the passed variable.)

Re: mod_perl / Apache::Registry accidental closures
by imp (Priest) on Jul 21, 2006 at 00:51 UTC
    I'm including an abuse of the mechanism in a separate comment, because it is not directly related to the tutorial.

    This is a bad thing to do, and any programmer who inherits the project from you will do horrible things to you if you use this trick

      My eyes! Ze goggles, zey do nothing!1!!

      Please, please, hide this behind spoiler tags :^)

Re: mod_perl / Apache::Registry accidental closures
by runrig (Abbot) on Jul 21, 2006 at 20:29 UTC
    When I was bitten by this, I wanted static variables for some functions. I had something like this (very simplified example):
    BEGIN { my $var; sub foo { return ++$var; } }
    The solution was to move all such subroutines into modules where they wouldn't get wrapped by Apache::Registry.

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: perltutorial [id://562746]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this?Last hourOther CB clients
Other Users?
Others goofing around in the Monastery: (3)
As of 2024-04-25 07:52 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?

    No recent polls found