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

On our web site we have two parts that run side by side.

The first, which outputs the general pages the site has, uses mod_rewrite to direct any URL ending in .phtml through a Perl script that pulls together the relevant templates and outputs the page. Essentially this means that /homepage.phtml becomes /index.pl?action=homepage and the file called homepage is loaded and processed. This runs through mod_perl and Apache::Registry (or ModPerl::Registry when under Apache2/mod_perl2).

The second part is a Perl script which uses threads.

use threads; use threads::shared; use Thread::Running;

We have a series of modules that connect to different supplier systems, like foo::system1, foo::system2 etc. The Perl script which uses threads knows which of these suppliers we need to connect to and then creates a new thread for each of the calls, so that all the communication with the suppliers happens at the same time. We do this via a function in the script called search...

sub search { # ... code above here takes in the params eval { eval("use " . $enginename); $enginehandler = $enginename->new(); $results{$engine} = $enginehandler->search($foo, $bar); }; }

The engine name is passed into the sub (hence the eval'd use line) and the whole call is wrapped in an eval to trap errors (the suppliers have a habit of changing their system or being offline and breaking, so we don't want the whole system to fall over just because one of them isn't working). The results hash that the reply goes into is shared at the top of the script using threads::shared.

share(%results);

After all the threads are created, which we do with...

threads->create(\&search, $foo, $bar);

We wait for 30 seconds and then grab everything that's been put into the shared hash, store that, and then move onto the next part of the process (which is a new script, which doesn't use threads).

Running under mod_cgi, this all works fantastically. When under mod_perl however, things aren't quite so peachy.

Under mod_perl, the search itself still works fine. All the threaded stuff seems to work great, it's our first script that starts failing, the one that handles the .phtml URLs. At random intervals after mod_perl has been enabled for the threads.pl script and searches are occuring, accessing a page like /homepage.phtml returns a blank page. The Apache access log shows that /homepage.phtml has been requested, the error log shows nothing, but the browser gets nothing returned, not even a content-type. Occassionally, accessing other Perl scripts on the server causes the browser to pop-up a dialog asking where you'd like to save it, again, as if no content-type was actually getting returned.

I put some warns in the index.pl script that mod_rewrite pushes through, and on the times where I get a blank page, they aren't sent to the error log, meaning that Apache isn't even executing the script. I changed the Apache LogLevel to debug instead of warn, and that didn't shed any light on the matter either.

I initially thought this was a problem with Apache 1.3/mod_perl1, thinking that they weren't setup for threads under the pre-fork model. So I installed Apache2, migrated everything over to that and the threaded worker module, but the results are essentially the same. As soon as we switch to SetHandler perl-script instead of cgi-script and tell it to use ModPerl::Registry, pages fail to come up.

I've thought that maybe the threads are dying with errors which are getting caught by the eval and the process that was running that thread is then the one Apache is picking to server the next .phtml request, only to find out it's unable to use it when it's already too late. But that's me thinking of logical reasons rather than knowing anything specific.

I've tried everything I can think of at this point, but getting nowhere, with no sign of anything similar elsewhere online. Not being able to swtich on mod_perl for the threads.pl script is really taxing the server and it's becoming a bigger problem with each day.

Replies are listed 'Best First'.
Re: mod_perl, threads, and blank pages
by perrin (Chancellor) on May 08, 2006 at 16:57 UTC
    Does it take a long time to get the blank page (i.e. something timing out) or does it happen immediately? It sounds like you need to do more tracing to figure out where it's getting stuck. PerlTrace might help, or adding more warns (possibly inside ModPerl::Registry if nothing in your script is getting called), or running the debugger.

      It happens immediately. And the access_log shows the item immediately, it just doesn't appear to have run anything.

      One thing I forgot to mention, was that under Apache2, there is an occassional message in the error_log about load.pm not being able to find index.pl (the script the .phtml URL's get pushed through) at line 256.

      Could not find file for 'Apache::ROOTwww_2ethesite_2eco_2euk::index_2epl' at /usr/local/share/perl/5.8.7/load.pm line 256.

Re: mod_perl, threads, and blank pages
by Codon (Friar) on May 08, 2006 at 21:47 UTC
    The system you are describing sounds strangely similar to one we were trying to build: Apache2/mod_perl2 + Perl iThreads, parallel searches to multiple threads, collecting data, returning it back to the user. The differences we had included spawning all threads at ChildInit and putting them into a wait loop, then signalling them when we wanted them to work. They'd go do stuff, put the data in a shared hash, then return to the wait loop.

    What we found is that Perl is not thread-safe. Let me repeat that: PERL IS NOT THREAD-SAFE! I say this with emphasis because after several long nights hunting down segfaults in the system, turning on debugging, adding evals to trap errors, banging my head, I came across this in the log:

    Bizarre ARRAY copy in caller() at Common::Logger::log_stuff ...
    Common::Logger is our internal logging package. log_stuff is the logging function. It calls CORE::caller(). This blew up. Evidently, the thread boundary is a bit like an event horizon and caller can't cross it without potentially breaking. I saw this message a few more times before we ditched threads entirely.

    I strongly encourage you to investigate the possibility of dead threads fouling your app. Then look at alternatives to a threaded model. We moved to a multi-tiered Apache platform. We have an Apache "front-end" that talks to an Apache "back-end" (which in turn can talk to itself; we have layers of parallel and serially parallel processing).

    When we mentioned to merlyn, TheDamien, and Nick Clark that we were looking at the threaded approach when we were at OSCON 2005, they all pretty much wished us luck. I do the same for you now.

    Ivan Heffner
    Sr. Software Engineer, DAS Lead
    WhitePages.com, Inc.
      What we found is that Perl is not thread-safe. Let me repeat that: PERL IS NOT THREAD-SAFE!

      That sentance makes no sense at all.

      Bizarre ARRAY copy in caller() at Common::Logger::log_stuff ...

      Common::Logger is our internal logging package. log_stuff is the logging function. It calls CORE::caller(). This blew up.

      Hmm. Did you report this 'bug'?

      It's quite possible to generate the 'Bizarre ARRAY copy' bug in perfectly ordinary, non-threaded bad code. What's more, perl will often not give you much in the way of clues as to what you are doing wrong when it happens.

      I don't know what version of Perl you were using, but it seems quite unlikely that using caller with threads is a problem. Set the loop counters in the following code to any values you like; or increase the depth of sub calls as far as you like, and it will run for as long as you let it without producing a "bizarre array copy error". Or any other error.

      #! perl -slw use strict; use threads; sub t1{ printf "%3d:t1: %s\n", threads->tid, join '|', grep defined, calle +r(1); return } sub t2 { t1(); printf "%3d:t2: %s\n", threads->tid, ''; '|', grep defined, calle +r(1); return } sub t3 { for( 1 .. 100 ) { t2(); printf "%3d:t3: %s\n", threads->tid, join '|', grep defined, +caller(1); } return; } my @threads = map{ threads->create( \&t3 ) } 1 .. 100; sleep 3; $_->join for @threads;

      It seems much more likely that your Common::Logger class was not written to be called from multiple threads. Indeed, the most likely scenario is that you were passing objects between threads. That's a documented no no.

      Most modern cars are pretty safe, but if you attempt to climb trees in them, it's likely to be unsuccessful; but saying cars are useless because your tree climbing attempt went ary, is unlikely to garner much support, even from the tree-hugging, cars-are-evil crowd.


      Examine what is said, not who speaks -- Silence betokens consent -- Love the truth but pardon error.
      Lingua non convalesco, consenesco et abolesco. -- Rule 1 has a caveat! -- Who broke the cabal?
      "Science is about questioning the status quo. Questioning authority".
      In the absence of evidence, opinion is indistinguishable from prejudice.

      The threads themselves seem to work fine until mod_perl is switched on. The application is live in it's non mod_perl version at the moment. But yes, it took us a long time to get to the stage where we got something other than bizarre errors in the log. And with mod_perl, they just get ever stranger.

      We actually started as non-threading, using a simple Perl powered web server (HTTP::Server based I think it was) plus LWP::Paralllel on the front-end to connect to it with different parameters, which it then went off and processed. But it all became too slow and unwieldy as the number of searches increased and the Perl webserver forked more and more. Returning to an LWP::Parallel process connecting to a more stable backend (like a light web server) is tempting, if only for scalability reasons.

      But darnit, when you've come this far, sometimes you just want things to work. :)

Re: mod_perl, threads, and blank pages
by blahblahblah (Priest) on May 09, 2006 at 02:07 UTC
    We had a completely unrelated problem running mod_perl & mod_cgi together (details here). Our solution: install two separate apaches. The "front-end" one serves all the cgi requests and uses mod_proxy to forward other requests to the back-end mod_perl apache on a different port.