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 |