package Catalyst::Plugin::AutoRestart;
use strict;
use warnings;
# The same accessor that Catalyst core uses
use base qw/Class::Data::Accessor/;
use Class::C3;
use Text::SimpleTable;
use Proc::ProcessTable;
__PACKAGE__->mk_classaccessor(qw/_autorestart_state/);
our $VERSION = '0.92';
=head1 NAME
Catalyst::Plugin::AutoRestart - Catalyst plugin to restart server
processes when specified memory threshold is reached
=head1 SYNOPSIS
use Catalyst qw/AutoRestart/;
__PACKAGE__->config->{Plugin::AutoRestart} = {
active => '1',
check_each => '20',
max_bits => 576716800,
min_handled_requests => '150',
}
active 1
check_each 20
max_bits 576716800
min_handled_requests 150
=head1 DESCRIPTION
Catalyst plugin to force the application to restart server processes when they reach
a configurable memory threshold. Memory checks are performed every 'N' requests. This is
intended as a band-aid to deal with problems like memory leaks; it's here to buy you
time to find and solve the underlying issues.
=head1 CONFIGURATION
=head2 active
This is used to turn the plugin on and off
=head2 check_each
This is the number of requests to wait between checks
=head2 min_handled_requests
Minimum application requests before process size check starts occurring.
This is to prevent your application processes from exiting immediately in
case your application is bigger than your max_bits limit.
The default is 500 requests
=head2 max_bits
This is the size virtual memory can grow to before triggering a restart
The default is 524288000 bits (500 mb)
=head2 size_field
Which size field to measure. Defaults to C. Other values are anything
that L has an accessor for, which depends on your
OS. Most people will want C (virtual memory size) or C
(resident set size)
=head1 SEE ALSO
For trying to solve memory leaks see L
=head1 EXTENDED METHODS
The following methods are extended from the main Catalyst application class.
=head2 setup
Create sane defaults
=cut
sub setup {
my $c = shift @_;
my $config = $c->config->{'Plugin::AutoRestart'} || {};
$c->_autorestart_state( {
_process_table => Proc::ProcessTable->new,
max_bits => 524288000,
min_handled_requests => 500,
size_field => 'size',
%$config
} );
return $c->next::method(@_)
}
=head2 handle_request
Count each handled request and when a threshold is met, restart.
=cut
sub handle_request {
my ($c, @args) = (shift, @_);
my $ret = $c->next::method(@args);
my $state = $c->_autorestart_state;
return $ret
unless $state->{active};
my $check_each = $state->{check_each};
if( ($Catalyst::COUNT >= $state->{min_handled_requests}) && ($Catalyst::COUNT % $check_each) == 0 ) {
$c->log->warn('Checking Memory Size.');
my $size = $c->_debug_process_table($c);
$c->log->warn("Found size is $size");
if(defined $size && $size > $state->{max_bits} ) {
# this actually wont output to log since it exits
$c->log->warn("$size is bigger than: ".$state->{max_bits}. " exiting now...");
$c->log->_flush if $c->log->can("_flush");
exit(0);
}
$c->log->_flush if $c->log->can("_flush");
}
return $ret;
}
=head2 _debug_process_table
Send to the log the full running process table and return the size of the
process
=cut
sub _debug_process_table {
my ($c) = @_;
my $state = $c->_autorestart_state;
foreach my $p ( @{$state->{_process_table}->table} ) {
next
unless $p->pid == $$;
my $table = new Text::SimpleTable( [ 6, 'PID' ], [ 12, 'VIRT' ], [ 12, 'RES' ], [ 15, 'COMMAND' ] );
$table->row($p->pid, $p->size, $p->rss, $p->cmndline);
$c->log->warn("Process Info:\n" . $table->draw);
my $fld = $state->{size_field};
return $p->$fld;
}
return 0;
}
=head1 AUTHORS
John Napiorkowski
John Goulah
=head1 COPYRIGHT
This program is free software; you can redistribute
it and/or modify it under the same terms as Perl itself.
=cut
1;