File Coverage

blib/lib/FCGI/Engine/ProcManager/Constrained.pm
Criterion Covered Total %
statement 1 3 33.3
branch n/a
condition n/a
subroutine 1 1 100.0
pod n/a
total 2 4 50.0


line stmt bran cond sub pod time code
1             package FCGI::Engine::ProcManager::Constrained;
2 1     1   2299 use Moose;
  0            
  0            
3              
4             use Config;
5             use Class::Load ();
6             use Try::Tiny;
7              
8             extends 'FCGI::Engine::ProcManager';
9              
10             sub BUILD {
11             my $self = shift;
12             if ($self->sizecheck_num_requests && ! _can_check_size()) {
13             confess "Cannot load size check modules for your platform: sizecheck_num_requests > 0 unsupported";
14             }
15             }
16              
17             has max_requests => (
18             isa => 'Int',
19             is => 'ro', # FIXME - This is fuck ugly.
20             default => sub { $ENV{PM_MAX_REQUESTS} || 0 },
21             );
22              
23             has request_count => (
24             isa => 'Int',
25             is => 'ro',
26             traits => ['Counter'],
27             handles => {
28             _reset_request_counter => 'reset',
29             _inc_request_counter => 'inc',
30             },
31             init_arg => undef,
32             default => 0,
33             );
34              
35             has [qw/
36             sizecheck_num_requests
37             max_process_size
38             min_share_size
39             max_unshared_size
40             /] => (
41             isa => 'Int',
42             is => 'ro',
43             default => 0,
44             );
45              
46             augment server_init => sub {
47             my $self = shift;
48             $self->_reset_request_counter();
49             };
50              
51             augment post_dispatch => sub {
52             my $self = shift;
53             $self->exit("safe exit after max_requests (" . $self->max_requests . ")")
54             if ($self->max_requests and $self->_inc_request_counter == $self->max_requests);
55              
56             if ($self->sizecheck_num_requests
57             and $self->request_count # Not the first request
58             and $self->request_count % $self->sizecheck_num_requests == 0
59             ) {
60             $self->exit("safe exit due to memory limits exceeded after " . $self->request_count . " requests")
61             if $self->_limits_are_exceeded;
62             }
63             };
64              
65             sub _limits_are_exceeded {
66             my $self = shift;
67              
68             my ($size, $share, $unshared) = $self->_check_size();
69              
70             return 1 if $self->max_process_size && $size > $self->max_process_size;
71             return 0 unless $share;
72             return 1 if $self->min_share_size && $share < $self->min_share_size;
73             return 1 if $self->max_unshared_size && $unshared > $self->max_unshared_size;
74              
75             return 0;
76             }
77              
78              
79             # The following code is wholesale is nicked from Apache::SizeLimit::Core
80              
81             sub _check_size {
82             my $class = shift;
83              
84             my ($size, $share) = $class->_platform_check_size();
85              
86             return ($size, $share, $size - $share);
87             }
88              
89             sub _load {
90             my $mod = shift;
91             try { Class::Load::load_class($mod); 1; }
92             }
93             our $USE_SMAPS;
94             BEGIN {
95             my ($major,$minor) = split(/\./, $Config{'osvers'});
96             if ($Config{'osname'} eq 'solaris' &&
97             (($major > 2) || ($major == 2 && $minor >= 6))) {
98             *_can_check_size = sub () { 1 };
99             *_platform_check_size = \&_solaris_2_6_size_check;
100             *_platform_getppid = \&_perl_getppid;
101             }
102             elsif ($Config{'osname'} eq 'linux' && _load('Linux::Pid')) {
103             *_platform_getppid = \&_linux_getppid;
104             *_can_check_size = sub () { 1 };
105             if (_load('Linux::Smaps') && Linux::Smaps->new($$)) {
106             $USE_SMAPS = 1;
107             *_platform_check_size = \&_linux_smaps_size_check;
108             }
109             else {
110             $USE_SMAPS = 0;
111             *_platform_check_size = \&_linux_size_check;
112             }
113             }
114             elsif ($Config{'osname'} =~ /(?:bsd|aix)/i && _load('BSD::Resource')) {
115             # on OSX, getrusage() is returning 0 for proc & shared size.
116             *_can_check_size = sub () { 1 };
117             *_platform_check_size = \&_bsd_size_check;
118             *_platform_getppid = \&_perl_getppid;
119             }
120             else {
121             *_can_check_size = sub () { 0 };
122             }
123             }
124              
125             sub _linux_smaps_size_check {
126             my $class = shift;
127              
128             return $class->_linux_size_check() unless $USE_SMAPS;
129              
130             my $s = Linux::Smaps->new($$)->all;
131             return ($s->size, $s->shared_clean + $s->shared_dirty);
132             }
133              
134             sub _linux_size_check {
135             my $class = shift;
136              
137             my ($size, $share) = (0, 0);
138              
139             if (open my $fh, '<', '/proc/self/statm') {
140             ($size, $share) = (split /\s/, scalar <$fh>)[0,2];
141             close $fh;
142             }
143             else {
144             $class->_error_log("Fatal Error: couldn't access /proc/self/status");
145             }
146              
147             # linux on intel x86 has 4KB page size...
148             return ($size * 4, $share * 4);
149             }
150              
151             sub _solaris_2_6_size_check {
152             my $class = shift;
153              
154             my $size = -s "/proc/self/as"
155             or $class->_error_log("Fatal Error: /proc/self/as doesn't exist or is empty");
156             $size = int($size / 1024);
157              
158             # return 0 for share, to avoid undef warnings
159             return ($size, 0);
160             }
161              
162             # rss is in KB but ixrss is in BYTES.
163             # This is true on at least FreeBSD, OpenBSD, & NetBSD
164             sub _bsd_size_check {
165              
166             my @results = BSD::Resource::getrusage();
167             my $max_rss = $results[2];
168             my $max_ixrss = int ( $results[3] / 1024 );
169              
170             return ($max_rss, $max_ixrss);
171             }
172              
173             sub _win32_size_check {
174             my $class = shift;
175              
176             # get handle on current process
177             my $get_current_process = Win32::API->new(
178             'kernel32',
179             'get_current_process',
180             [],
181             'I'
182             );
183             my $proc = $get_current_process->Call();
184              
185             # memory usage is bundled up in ProcessMemoryCounters structure
186             # populated by GetProcessMemoryInfo() win32 call
187             my $DWORD = 'B32'; # 32 bits
188             my $SIZE_T = 'I'; # unsigned integer
189              
190             # build a buffer structure to populate
191             my $pmem_struct = "$DWORD" x 2 . "$SIZE_T" x 8;
192             my $mem_counters
193             = pack( $pmem_struct, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 );
194              
195             # GetProcessMemoryInfo is in "psapi.dll"
196             my $get_process_memory_info = new Win32::API(
197             'psapi',
198             'GetProcessMemoryInfo',
199             [ 'I', 'P', 'I' ],
200             'I'
201             );
202              
203             my $bool = $get_process_memory_info->Call(
204             $proc,
205             $mem_counters,
206             length $mem_counters,
207             );
208              
209             # unpack ProcessMemoryCounters structure
210             my $peak_working_set_size =
211             (unpack($pmem_struct, $mem_counters))[2];
212              
213             # only care about peak working set size
214             my $size = int($peak_working_set_size / 1024);
215              
216             return ($size, 0);
217             }
218              
219             sub _perl_getppid { return getppid }
220             sub _linux_getppid { return Linux::Pid::getppid() }
221              
222             no Moose;
223             __PACKAGE__->meta->make_immutable;
224             1;
225              
226             __END__
227              
228             =pod
229              
230             =head1 NAME
231              
232             FCGI::Engine::ProcManager::Constrained - FastCGI applications with memory and number of request limits.
233              
234             =head1 DESCRIPTION
235              
236             A constrained process manager that restarts child workers after a number of requests
237             or if they use too much memory.
238              
239             Most of the memory usage code is stolen from L<Apache2::SizeLimit>.
240              
241             =head1 ATTRIBUTES
242              
243             =head2 max_requests
244              
245             The number of requests a child process can handle before being terminated.
246              
247             0 (the default) means let child processes do an infinite number of requests
248              
249             =head2 sizecheck_num_requests
250              
251             The number of requests between a check on the process size taking place.
252              
253             0 (the default) means never attempt to check the process size.
254              
255             =head2 max_process_size
256              
257             The maximum size of the process (both shared and unshared memory) in KB.
258              
259             0 (the default) means unlimited.
260              
261             =head2 max_unshared_size
262              
263             The maximum amount of memory in KB this process can have that isn't Copy-On-Write
264             shared with other processes.
265              
266             0 (the default) means unlimited.
267              
268             =head2 min_share_size
269              
270             The minimum amount of memory in KB this process can have Copy-On-Write from
271             it's parent process before it is terminate.
272              
273             =head1 METHODS
274              
275             I will fill this in more eventually, but for now if you really wanna know,
276             read the source.
277              
278             =head1 SEE ALSO
279              
280             =over
281              
282             =item L<FCGI::Engine::ProcManager>
283              
284             =item L<Apache2::SizeLimit>.
285              
286             =back
287              
288             =head1 BUGS
289              
290             All complex software has bugs lurking in it, and this module is no
291             exception. If you find a bug please either email me, or add the bug
292             to cpan-RT.
293              
294             =head1 AUTHOR
295              
296             Tomas Doran E<lt>bobtfish@bobtfish.netE<gt>
297              
298             =head1 COPYRIGHT AND LICENSE
299              
300             Code sections copied from L<Apache2::SizeLimit> are Copyright their
301             respective authors.
302              
303             Copyright 2007-2010 by Infinity Interactive, Inc.
304              
305             L<http://www.iinteractive.com>
306              
307             This library is free software; you can redistribute it and/or modify
308             it under the same terms as Perl itself.
309              
310             =cut