Re: FastCGI Expert Sought

Matthew Weigel <[email protected]>
Newsgroups gmane.comp.web.fastcgi.devel
Message-ID <[email protected]>
Ross Richey wrote:

> So yes this might be something which needs to be handled at the Lighttpd
> level.  But before we do that I just want to ask the conceptual question:
> 
> Is there some method of nicely killing and respawning a FastCGI process?

I run lighttpd on my personal server and do my personal web development
as FastCGI; however, I don't rely on lighttpd's process management
functionality to do it[1].

If you do use lighttpd's own process management (e.g., specifying in
lighttpd.conf the 'bin-path' for a FastCGI process)... first of all,
have you looked at
http://redmine.lighttpd.net/projects/lighttpd/wiki/Docs:ModFastCGI ?
You should be able to set a minimum number of processes, and a maximum
number of processes, for that FastCGI server.  I *think* - but again, I
don't use this part of lighttpd myself - that if you simply configure
your Perl program to exit after handling X number of requests, lighttpd
will spawn new processes once the number of remaining processes drops
below the minimum.

PHP has the functionality you're asking about (each child process dies
after X requests) built in, without needing to be spawned/managed by
lighttpd, and I wrote a small Perl application that does it as well.
The traffic I get for this particular web app is minimal, so I've never
needed to take the PHP approach of having a master process that manages
multiple children who occasionally restart; it's definitely possible though.

> The reason I asked for an expert and offered money is that we're fully
> expecting that we might have to get someone to actually add that
> functionality to FastCGI, but obviously I'm open to other ideas on
> fixing the problem.

Attached is the skeleton of the Perl application I wrote, minus the
actual HTML generation, form handling, etc.  It might help with writing
a Perl FastCGI server that isn't managed directly by lighttpd.  You
might have an easier time simply changing your existing code to exit
after X requests, but both options are worth considering.  It works for
me, but that's no guarantee. :-)

1. I do quite a bit to separate privileges and functionality; I have
lighttpd configured to chroot itself to /var/www and drop root
privileges when it starts, and for example PHP is spawned via lighttpd's
spawn-fcgi in a more restrictive chroot (like
/var/www/first.example.net/) as a more restricted user.
-- 
 Matthew Weigel
 hacker
 unique & idempot . ent

_______________________________________________
FastCGI-developers mailing list
FastCGI-developers-xGejAJT2w6xVgU18Zptdi0EOCMrvLtNR@public.gmane.org
http://mailman.pins.net/mailman/listinfo.cgi/fastcgi-developers
foo_srv.pl (application/x-perl, 3.3 KB)
#! /usr/bin/perl

# (c) 2009, Matthew Weigel
#  All rights reserved.
# 
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation files
# (the "Software"), to deal in the Software without restriction,
# including without limitation the rights to use, copy, modify, merge,
# publish, distribute, sublicense, and/or sell copies of the Software,
# and to permit persons to whom the Software is furnished to do so,
# subject to the following conditions:
# 
# The above copyright notice and this permission notice shall be
# included in all copies or substantial portions of the Software.
# 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
# BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
# ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE.

use strict;
use Fcntl qw(:flock);
use English '-no_match_vars';

use FCGI;
use CGI::Fast;

use Proc::Daemon;
use Config::General;

my $config_file = "/etc/foo.cfg";

if ($ARGV[0] eq '-f') {
	$config_file = $ARGV[1];
}

my %config
	= new Config::General('-ConfigFile'        => $config_file,
	                      '-AllowMultiOptions' => 'no',
	                      '-LowerCaseNames'    => 1,
	                      '-SplitPolicy'       => 'equalsign',
	                      '-CComments'         => 0,
	                     )
	->getall();


Proc::Daemon::Init();

my $procname = $config{'procname'} || "foo";
my $pidfile = $config{'pidfile'} || "/var/run/$procname.pid";
my $logfile = $config{'logfile'} || "/var/log/$procname.log";
my $socket_path = ":" . $config{'port'};

open(PID, ">$pidfile") or die "Can't open PID file: $!";
my $lock_res = flock(PID, LOCK_EX | LOCK_NB);
die "Can't lock $pidfile: $!" unless $lock_res;
select((select(PID), $OUTPUT_AUTOFLUSH=1)[0]);
print PID "$PID";

open(LOG, ">>$logfile");
select((select(LOG), $OUTPUT_AUTOFLUSH=1)[0]);
log_message("PID is $PID.");

my $socket = FCGI::OpenSocket( $socket_path, 100 );
$CGI::Fast::Ext_Request = FCGI::Request(\*STDIN, \*STDOUT, \*STDERR,
                                        \%ENV, $socket, 1);

$SIG{'TERM'} = sub {
	log_message("Received SIGTERM, exiting.");
	truncate(PID, 0);
	flock(PID, LOCK_UN);
	close(PID);
	close LOG;
	$CGI::Fast::Ext_Request->Flush();
	$CGI::Fast::Ext_Request->LastCall();
	exit();
};

my $counter = 0;
my $reason = "Restarting process to avoid potential memory leaks.";
while (my $cgi = new CGI::Fast) {
	log_message("Handling request.");
	process_req($cgi);
	$CGI::Fast::Ext_Request->Finish();

	$counter++;
	if ($counter > 1_000) {
		last;
	}
	if (-M $0 < 0) {
		$reason = "Script changed, restarting to incorporate changes.";
		last;
	}
}

log_message($reason);
truncate(PID, 0);
flock(PID, LOCK_UN);
close(PID);
close LOG;
$CGI::Fast::Ext_Request->Flush();
$CGI::Fast::Ext_Request->LastCall();
FCGI::CloseSocket($socket);
exec($0, @ARGV) or print STDERR "couldn't exec foo: $!";

sub process_req
{
# probably do something here
}

sub log_message
{
	my ($message) = @_;
	print LOG gmtime() . ": $message" . "\n";
}
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.