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

T'is me again. I'm still trying to grow into this extra large monk's robe, so please bear with me.

I have a small piece of code running under mod_perl and Apache::Registry (using a module is not an option at this point) and I've run into a very odd caching issue.

When I reload the same page over and over and over, printing out what $vars{action} should contain, I get very incorrect results.

My initial page is 'index.pl?action=home'. I'm exposing this variable with:

print p("\$vars{action} passed was: $vars{action}\n") if $vars{action} +;

When I reload that page successively, I get:

$vars{action} passed was: news $vars{action} passed was: home $vars{action} passed was: $vars{action} passed was: dl

..which matches any of the various vars that have been passed in the past to this script. It seems like past "clicks" to this CGI are persistant. I understand that these should be persistant for the life of the server process, but $vars should not be persistant, should it? The relevant code is as follows:

use strict; use warnings; use diagnostics; use Env; # Send errors to browser use CGI::Carp qw( fatalsToBrowser ); use CGI qw(:standard); my $query = CGI->new(); my %vars = $query->Vars(); # CGI.pm print header(), start_html(), and basic HTML goes here print p("\$vars{action} passed was: $vars{action}\n") if $vars{action} +;

Is there something I'm not seeing here? Should $vars be persistant throughout reloads? With more than one server process and more than one user hitting different options from the site, this could get very confusing and very ugly if a user clicks 'Download' and gets the 'News' page from the previous user's clicking.

I tried autoflushing at the top, but that didn't help. All of the possible variables are locals, not global, with the exception of those started in cgi-bin/startup.pl, as required by the Apache::Registry/mod_perl documentation.

Did I miss something?

Replies are listed 'Best First'.
Re: "caching" issue with mod_perl?
by dsheroh (Monsignor) on Jun 10, 2002 at 20:08 UTC
    No solid ideas on why this is happening (I've done modules with mod_perl and plain CGI, but never worked with Apache::Registry), but a couple ideas which might make your script work, or at least provide useful new information:
    • Put a set of {braces} around the body of your script so that %vars will go out of scope between requests
    • Try explicitly setting my %vars = () before initializing it from $query->Vars()
    • If you don't have a strong need to pull your CGI parameters in with Vars(), try dropping %vars entirely and using $query->param('action') instead
Re: "caching" issue with mod_perl?
by perrin (Chancellor) on Jun 10, 2002 at 21:11 UTC
    If that print is actually inside of a subroutine, then you would have a closure problem which would keep the first value passed in forever. If it's not inside a sub and is just exactly as you show here with no hidden changes to %vars happening in those subs you mention in your comment, then I would suspect that the method $query->Vars() may have a bug when used with mod_perl. You could try using $query->param('action') instead and see if that gives a different result.

    I would also be very suspicious of the Env module. It might be loading environment variables into globals and not clearing them between invocations. I don't see how this would affect your %vars issue, but it could be another caching problem. Just use %ENV instead, which is set up for you by mod_perl.

      You could try using $query->param('action') instead and see if that gives a different result.

      Here's another confusing issue: I have seen and talked to many people who recommend the two common ways of doing this, as a hash, and as a list; each with their own "religion" as to what is right.

      • Is there any benefit/performance gain/security for using one over the other?
      • What if there are identically-named keys passed in the URI, with different values?

      I've tried both, and the hash method seems to be 200µseconds faster. Nothing substantial, but over the course of 40,000 hits a day, that could be enough to have an effect.

      I've managed to gut the original code down to the basics, no graphics, but the same identical structure, and the problem with the "caching" of $vars{action} seems to have subsided... but the other side effect I'm seeing now is many stale mysql processes left open. I'm explicitly doing a $dbh->disconnect;, but I don't think they're being freed.

      Lastly, I managed to hook a very crude rudimentary timer using Time::HiRes into the page, so I could test the timings of refreshes. Something very curious started to occur. In some of the reloads of the same page, the page will report results as:

      Elapsed time for vars was: 25.304 µsec's

      While other successive reloads will produce:

      Elapsed time for vars was: 5321462136156.284 µsec's

      Is this time shown from one of the older httpd processes in the queue that haven't seen the data yet? The difference is so drastic, that's the only thing I can come up with. The timer I've got looks like this:

      use Time::HiRes qw(gettimeofday tv_interval); my $t0 = [gettimeofday]; # Time::HiRes (start time) my $t1 = ''; # (end time) # Draw page, do SQL query here $t1 = [gettimeofday]; # end timer my $elapsed = tv_interval ($t0); $elapsed = $elapsed * 1000; print p("Elapsed time for vars was: $elapsed µsec's\n");

      I'm going to have to cut this down into smaller sections, and start adding one line at a time, to see where this is failing.

      Boggle.

        I gave up using CGI.pm years ago when I went to mod_perl and never looked back. I use $r->args() or Apache::Request. I don't think the performance difference you describe is enough to merit choosing one way of reading args over the other. Pick whichever one is easier to read for you. Either way, I think CGI.pm will give you an array ref if you send in identically named args.

        Are you using Apache::DBI? That would explain why you have connections open to MySQL.

        As for the timing, it's hard to say based on the code you've shown. It's possible that some processes are failing to get database connections, or your machine is swapping, or any number of other things. Your plan to take things out until it stops and then slowly add them back in is a good idea. That always works for me.