Perl FastCGI
From DreamHost
Extensions you might use for Perl FastCGI modules are .fpl and .fcgi.
If you're looking to use Mason with FastCGI, check out Mason FastCGI.
Necessary Modules
In order to get Perl working with FastCGI, you need to install the FCGI module:
wget http://www.cpan.org/modules/by-module/FCGI/FCGI-0.67.tar.gz tar xvfz FCGI-0.67.tar.gz cd FCGI-0.67 perl Makefile.PL PREFIX=/home/myhome --use-installed make make test && make install
- PREFIX is the location where you want to install the module, the usual structure (lib, share, man) will be created there. Of course, you need to have write permissions to this directory.
- --use-installed uses the fcgi library already installed on the system, without it fcgi will be compiled from included source (which theoretically also works just fine).
For your scripts to find it, add this line before you load the modules:
use lib '/home/myhome/lib/perl/5.8.4';
Of course /home/myhome must match the directory you specified as PREFIX, and 5.8.4 should be whatever the installed perl version is.
Script Example
To test that FastCGI is configured and working properly, try this simple 'hello world' program. Paste this text into a file with the ending .fcgi.
#!/usr/bin/perl
use lib qw( /home/myhome/lib/perl/5.8.4 );
use FCGI;
use Socket qw( :crlf ); # server agnostic line endings in $CRLF
my $counter = 0;
while ( FCGI::accept() >= 0 ) {
$counter++;
print
"Content-Type: text/plain",
$CRLF,
$CRLF,
"Hello World, in Perl FastCGI!",
$CRLF,
"I am process $$.",
$CRLF,
"I have served $counter request(s).",
$CRLF;
}
The $CRLF is a server neutral line ending equivalent to "\015\012." It will always work. "\n" or even "\r\n" sometimes will not.
If you change the extension of this script to .pl or .cgi so it's handled by regular CGI, you'll notice that the PID is different on every request, and the request counter stays at 1.
Handing off request fulfillment to a fork()ed child
DreamHost limits you to 5 concurrent FastCGI processes per application (or per .fcgi file). Therefore, if you have a long running task, such as requesting authorization from a payment gateway, and 5 or more such requests come in around the same time, future requests will have to wait for one of those 5 slots to free up. (With regular CGI, there is no such concurrency issue on DreamHost.)
To solve this problem, you could spawn a child process and pass off fulfillment of the long-running request to the child, leaving the parent free to handle further requests. So, if you know that the current task may take a while or may time out, you would do something like this to let other requests through in the meantime:
#!/usr/bin/perl -w
# This code hands off a request to a spawned child, for long-running tasks that
# should not hold up the request queue of a FastCGI application.
$|=1;
use FCGI;
use Socket qw( :crlf );
use strict;
use POSIX 'setsid';
my $child;
my $parent = $$;
my $count = 0;
my $busy = 0;
my $lastreq = 0;
sub doomed { $lastreq = 1; exit(0) if !$busy; }
$SIG{USR1} = \&doomed;
$SIG{TERM} = \&doomed;
$SIG{PIPE}= 'IGNORE'; # continue processing on client disconnect (i think)
$SIG{CHLD}= 'IGNORE'; # prevent children from becoming zombies
my $long_running_task = 1; # for this example, assume all requests are long-running.
my $request = FCGI::Request();
*FCGI::DESTROY = sub {}; # This prevents a race condition in which the
# parent's $request is destroyed, Finish()ing and
# thus closing the socket/handles of both parent and
# child. Now, Finish() must be called explicitly as
# it will not be called upon destruction of a request.
while ($busy = ($request->Accept() >= 0)) {
print "Content-type: text/plain",$CRLF,$CRLF;
if ($long_running_task) {
$request->Detach();
$parent = $$;
$child = fork();
if (!defined $child) { die "fork failed: fork table full or process limit reached?"; }
elsif ($child == 0) {
setsid;
$request->Attach(); # continue request fulfillment in CHILD.
$request->LastCall(); # this is the last request this $request object will handle.
# child stuff - process request here
print "Hello world, I am child $$ of parent $parent .", $CRLF;
$request->Finish(); # DO NOT FORGET TO CALL THIS
# OR YOU WILL RUN OUT OF SOCKETS/DESCRIPTORS EVENTUALLY
exit(0); # die after processing long-running task
} else {
# parent stuff - continue handling new requests
$request = FCGI::Request(); # destroy current request object and create new one.
}
}
$busy = 0;
last if $lastreq;
}
The two key issues when fork()ing with the FCGI module are: (1) Detach() before you fork, then Attach() in the process that should continue handling the request. (2) Delete the *FCGI::DESTROY method before you fork or in the parent. The DESTROY method calls Finish() when $request is destroyed in the parent to make room for a new $request for the next request. If the parent calls Finish() before the child has completed handling the request, since both parent and child will share file descriptors or sockets to FastCGI for the current request, then the child will lose its STDIN/STDOUT! Therefore, we explicitly call Finish() when the child is done, and never call it in the parent after a fork. (Finish() results in the output buffer being sent to the client/flushed.)
The little race condition/DESTROY implicit Finish() problem is not readily apparent, so I hope this helps you...

