Skip to content

Commit

Permalink
Item13010:Item12909: fixes fcgi instabiltiy
Browse files Browse the repository at this point in the history
- when run under ProcManager
- add a spec file as well
- removed taint mode from respawned workers
  • Loading branch information
MichaelDaum committed Aug 29, 2014
1 parent 7655f57 commit c59dd23
Show file tree
Hide file tree
Showing 10 changed files with 383 additions and 36 deletions.
7 changes: 7 additions & 0 deletions FastCGIEngineContrib/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
FastCGIEngineContrib.md5
FastCGIEngineContrib.sha1
FastCGIEngineContrib.tgz
FastCGIEngineContrib.txt
FastCGIEngineContrib.zip
FastCGIEngineContrib_installer
FastCGIEngineContrib_installer.pl
23 changes: 15 additions & 8 deletions FastCGIEngineContrib/bin/foswiki.fcgi
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/usr/bin/perl
#!/usr/bin/env perl
# Foswiki - The Free and Open Source Wiki, http://foswiki.org/
#
# Copyright (C) 2008-2014 Gilmar Santos Jr, jgasjr@gmail.com and Foswiki
Expand All @@ -24,6 +24,7 @@
# As per the GPL, removal of this notice is prohibited.

use strict;
use warnings;

BEGIN {
$Foswiki::cfg{Engine} = 'Foswiki::Engine::FastCGI';
Expand All @@ -36,25 +37,25 @@ use Getopt::Long;
use Pod::Usage;
use Foswiki;
use Foswiki::UI;
require Cwd;
use Cwd;

our ($script) = $0 =~ /^(.*)$/;
our ($dir) = Cwd::cwd() =~ /^(.*)$/;

eval { eval substr( $0, 0, 0 ) };
Foswiki::Engine::FastCGI::reExec() unless $@ =~ /^Insecure dependency in eval/;

my @argv = @ARGV;

my ( $listen, $nproc, $pidfile, $manager, $detach, $help, $quiet );
my ( $listen, $nproc, $max, $size, $check, $pidfile, $manager, $detach, $help, $quiet );
GetOptions(
'listen|l=s' => \$listen,
'nproc|n=i' => \$nproc,
'max|x=i' => \$max,
'check|c=i' => \$check,
'size|s=i' => \$size,
'pidfile|p=s' => \$pidfile,
'manager|M=s' => \$manager,
'daemon|d' => \$detach,
'help|?' => \$help,
'quiet|q' => \$quiet
'quiet|q' => \$quiet,
);

pod2usage(1) if $help;
Expand All @@ -74,7 +75,10 @@ $Foswiki::engine->run(
pidfile => $pidfile,
manager => $manager,
detach => $detach,
quiet => $quiet
quiet => $quiet,
max => $max,
size => $size,
check => $check,
}
);

Expand All @@ -89,6 +93,9 @@ foswiki.fcgi [options]
-n --nproc Number of backends to use, defaults to 1
-p --pidfile File used to write pid to
-M --manager FCGI manager class
-x --max Maximum requests served per server instance
-c --check Number of requests when to check the size of the server
-s --size Maximum memory size of a server before being recycled
-d --daemon Detach from terminal and keeps running as a daemon
-q --quiet Disable notification messages
-? --help Display this help and exits
Expand Down
3 changes: 2 additions & 1 deletion FastCGIEngineContrib/data/System/FastCGIEngineContrib.txt
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ $HTTP["url"] =~ "^%URL%/" {
}
</pre>

---++ Nginx
---+++ Nginx

In contrast to Apache or Lighttpd Nginx does not control the life time of the =foswiki.fcgi= backend process. Instead you will
have to start it yourself using the system's init process. The FCGI::ProcManager class will then take care of (re-)spawning
Expand Down Expand Up @@ -401,6 +401,7 @@ Dynamic servers are more useful when Foswiki access load on the server is low an
| Release: | %$RELEASE% |
| Version: | %$VERSION% |
| Change History: | |
| 29 Aug 2014 | (0.97) Foswikitask:Item13010 - fixed instability running under FCGI::ProcManager |
| 20 Feb 2014 | (0.96) Foswikitask:Item12755 - fixed socket not being closed properly on a reExec; work around error in FCGI.pm; added =quiet= parameter to suppress normal messages; fixed tainted pid filename; |
| 08 Sep 2011 | (0.95) Foswikitask:Item9957 - remove uninitialised value log message |
| 26 Oct 2010 | (0.94) Foswikitask:Item9902 - Adding more resources about how to get and install CPAN lib and mod_fcgid or mod_fastcgi. Also includes temporary fix from Foswikitask:Item1515: added maxRequests to ease memory leaks and fix for Foswikitask:Item9456: Taint error with foswiki.fcgi |
Expand Down
263 changes: 263 additions & 0 deletions FastCGIEngineContrib/lib/FCGI/ProcManager/Constrained.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,263 @@
package FCGI::ProcManager::Constrained;

use strict;
use warnings;

use Config;
use FCGI::ProcManager ();
our @ISA = qw( FCGI::ProcManager );

sub new {
my $proto = shift;

my $this = $proto->SUPER::new(@_);
$this->{max_requests} = 0
unless defined $this->{max_requests};

$this->{sizecheck_num_requests} = 0
unless defined $this->{sizecheck_num_requests};

$this->{max_size} = 0 unless defined $this->{max_size};

if ( $this->{sizecheck_num_requests} && !_can_check_size() ) {
$this->pm_warn(
"Cannot load size check modules for your platform: sizecheck_num_requests > 0 unsupported"
);
}

return $this;
}

sub max_requests {
shift->pm_parameter( 'max_requests', @_ );
}

sub sizecheck_num_requests {
shift->pm_parameter( 'sizecheck_num_requests', @_ );
}

sub max_size {
shift->pm_parameter( 'max_size', @_ );
}

sub handling_init {
my $this = shift;

$this->SUPER::handling_init();
$this->{_request_counter} = 0;
}

sub pm_post_dispatch {
my $this = shift;

$this->{_request_counter}++;

if ( $this->max_requests > 0
&& $this->{_request_counter} == $this->max_requests )
{
$this->pm_exit( "safe exit after max_requests ("
. $this->{_request_counter}
. ")" );
}
if (
$this->sizecheck_num_requests
&& $this->{_request_counter} # Not the first request
&& ( $this->{_request_counter} % $this->sizecheck_num_requests ) == 0
)
{
my $size = $this->_limits_are_exceeded;
$this->pm_exit( "safe exit due to memory limits exceeded after "
. $this->{_request_counter}
. " requests: size=$size (max="
. $this->max_size
. ")" )
if $size;
}
$this->SUPER::pm_post_dispatch();
}

sub _limits_are_exceeded {
my $this = shift;

my ( $size, $share, $unshared ) = $this->_check_size();

return $size if $this->max_size && $size > $this->max_size;

$this->pm_notify( "size=$size after $this->{_request_counter} requests" );

return 0 unless $share;

# FIXME
# return 1 if $this->min_share_size && $share < $this->min_share_size;
# return 1 if $this->max_unshared_size && $unshared > $this->max_unshared_size;

return 0;
}

# The following code is wholesale is nicked from Apache::SizeLimit::Core

sub _check_size {
my $class = shift;

my ( $size, $share ) = $class->_platform_check_size();

return ( $size, $share, $size - $share );
}

sub _load {
my $mod = shift;
$mod =~ s/::/\//g;
$mod .= '.pm';
eval { require($mod); 1; };
}
our $USE_SMAPS;

BEGIN {
my ( $major, $minor ) = split( /\./, $Config{'osvers'} );
if ( $Config{'osname'} eq 'solaris'
&& ( ( $major > 2 ) || ( $major == 2 && $minor >= 6 ) ) )
{
*_can_check_size = sub () { 1 };
*_platform_check_size = \&_solaris_2_6_size_check;
*_platform_getppid = \&_perl_getppid;
}
elsif ( $Config{'osname'} eq 'linux' ) {
if ( _load('Linux::Pid') ) {
*_platform_getppid = \&_linux_getppid;
}
else {
*_platform_getppid = \&_perl_getppid;
}

*_can_check_size = sub () { 1 };
if ( _load('Linux::Smaps') && Linux::Smaps->new($$) ) {
$USE_SMAPS = 1;
*_platform_check_size = \&_linux_smaps_size_check;
}
else {
$USE_SMAPS = 0;
*_platform_check_size = \&_linux_size_check;
}
}
elsif ( $Config{'osname'} =~ /(?:bsd|aix)/i && _load('BSD::Resource') ) {

# on OSX, getrusage() is returning 0 for proc & shared size.
*_can_check_size = sub () { 1 };
*_platform_check_size = \&_bsd_size_check;
*_platform_getppid = \&_perl_getppid;
}
else {
*_can_check_size = sub () { 0 };
}
}

sub _linux_smaps_size_check {
my $class = shift;

return $class->_linux_size_check() unless $USE_SMAPS;

my $s = Linux::Smaps->new($$)->all;
return ( $s->size, $s->shared_clean + $s->shared_dirty );
}

sub _linux_size_check {
my $class = shift;

my ( $size, $share ) = ( 0, 0 );
if ( open my $fh, '<', '/proc/self/statm' ) {
( $size, $share ) = ( split /\s/, scalar <$fh> )[ 0, 2 ];
close $fh;
}
else {
$class->_error_log("Fatal Error: couldn't access /proc/self/status");
}

# linux on intel x86 has 4KB page size...
return ( $size * 4, $share * 4 );
}

sub _solaris_2_6_size_check {
my $class = shift;

my $size = -s "/proc/self/as"
or $class->_error_log(
"Fatal Error: /proc/self/as doesn't exist or is empty");
$size = int( $size / 1024 );

# return 0 for share, to avoid undef warnings
return ( $size, 0 );
}

# rss is in KB but ixrss is in BYTES.
# This is true on at least FreeBSD, OpenBSD, & NetBSD
sub _bsd_size_check {

my @results = BSD::Resource::getrusage();
my $max_rss = $results[2];
my $max_ixrss = int( $results[3] / 1024 );

return ( $max_rss, $max_ixrss );
}

sub _win32_size_check {
my $class = shift;

# get handle on current process
my $get_current_process =
Win32::API->new( 'kernel32', 'get_current_process', [], 'I' );
my $proc = $get_current_process->Call();

# memory usage is bundled up in ProcessMemoryCounters structure
# populated by GetProcessMemoryInfo() win32 call
my $DWORD = 'B32'; # 32 bits
my $SIZE_T = 'I'; # unsigned integer

# build a buffer structure to populate
my $pmem_struct = "$DWORD" x 2 . "$SIZE_T" x 8;
my $mem_counters = pack( $pmem_struct, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );

# GetProcessMemoryInfo is in "psapi.dll"
my $get_process_memory_info =
new Win32::API( 'psapi', 'GetProcessMemoryInfo', [ 'I', 'P', 'I' ], 'I' );

my $bool =
$get_process_memory_info->Call( $proc, $mem_counters,
length $mem_counters,
);

# unpack ProcessMemoryCounters structure
my $peak_working_set_size =
( unpack( $pmem_struct, $mem_counters ) )[2];

# only care about peak working set size
my $size = int( $peak_working_set_size / 1024 );

return ( $size, 0 );
}

sub _perl_getppid { return getppid }
sub _linux_getppid { return Linux::Pid::getppid() }

1;

=head1 NAME
FCGI::ProcManager::Constrained - Process manager with constraints
=head1 SYNOPSIS
$this->{max_requests} = 1000;
$this-<{sizecheck_num_requests} = 10;
$this->{max_size} = 4096;
=head1 DESCRIPTION
Subclass of L<FCGI::ProcManager> which adds checks for memory limits
like L<Apache::SizeLimit>.
=head1 AUTHORS, COPYRIGHT AND LICENSE
See L<FCGI::ProcManager>.
=cut

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# ---+ Extensions
# ---++ FastCGIEngineContrib
# Note that these values only serve as a default and will be superseded by any values in /etc/default/foswiki.
# I.e. you will have to specify the size of the worker pool, that is the number of Foswiki backends spawned.

# **NUMBER**
# This is the maximum number of requests a backend is allowed to serve. Afterwards it will be killed and replaced
# with a new one. Set to -1 to disable this check.
$Foswiki::cfg{FastCGIContrib}{MaxRequests} = 100;

# **NUMBER**
# This is the maximum memory a child process is allowed to grow up to. Afterwards it will be killed and replaced
# with a new one. Set to zero to disable this check.
$Foswiki::cfg{FastCGIContrib}{MaxSize} = 0;

# **NUMBER**
# This is the number of requests after which a size check is performed. Use as hight number as possible as this is
# potentially costy operation that you don't want to pay on every request. Low values will result in a better
# size control of child processes; high values may give you a slightly better overall performance.
$Foswiki::cfg{FastCGIContrib}{CheckSize} = 10;

Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
bin/foswiki.fcgi 0755
data/System/FastCGIEngineContrib.txt 0644
lib/FCGI/ProcManager/Constrained.pm 0644
lib/Foswiki/Contrib/FastCGIEngineContrib/Config.spec 0644
lib/Foswiki/Contrib/FastCGIEngineContrib.pm 0644
lib/Foswiki/Engine/FastCGI.pm 0644
lib/Foswiki/Engine/FastCGI/ProcManager.pm 0644
data/System/FastCGIEngineContrib.txt 0644
tools/foswiki.defaults 0644
tools/foswiki.init-script 0644
tools/foswiki.defaults 0755
tools/foswiki.init-script 0755
Loading

0 comments on commit c59dd23

Please sign in to comment.