File Coverage

blib/lib/Net/Server/PreFork.pm
Criterion Covered Total %
statement 130 262 49.6
branch 37 128 28.9
condition 9 32 28.1
subroutine 17 30 56.6
pod 7 15 46.6
total 200 467 42.8


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             #
3             # Net::Server::PreFork - Net::Server personality
4             #
5             # Copyright (C) 2001-2017
6             #
7             # Paul Seamons
8             #
9             # This package may be distributed under the terms of either the
10             # GNU General Public License
11             # or the
12             # Perl Artistic License
13             #
14             # All rights reserved.
15             #
16             ################################################################
17              
18             package Net::Server::PreFork;
19              
20 2     2   22474 use strict;
  2         32  
  2         224  
21 2     2   18 use base qw(Net::Server::PreForkSimple);
  2         20  
  2         1418  
22 2     2   24 use Net::Server::SIG qw(register_sig check_sigs);
  2         8  
  2         130  
23 2     2   12 use POSIX qw(WNOHANG);
  2         6  
  2         16  
24 2     2   138 use IO::Select ();
  2         4  
  2         36  
25 2     2   1408 use Time::HiRes qw(time);
  2         3428  
  2         12  
26              
27 1     1 0 5 sub net_server_type { __PACKAGE__ }
28              
29             sub options {
30 1     1 0 25 my $self = shift;
31 1         50 my $ref = $self->SUPER::options(@_);
32 1         17 my $prop = $self->{'server'};
33 1         31 $ref->{$_} = \$prop->{$_} for qw(min_servers min_spare_servers max_spare_servers spare_servers
34             check_for_waiting child_communication check_for_spawn min_child_ttl);
35 1         4 return $ref;
36             }
37              
38              
39             sub post_configure {
40 1     1 1 5 my $self = shift;
41 1         3 my $prop = $self->{'server'};
42 1         9 $self->SUPER::post_configure;
43              
44 1         7 my $d = {
45             # max_servers is set in the PreForkSimple server and defaults to 50
46             min_servers => 5, # min num of servers to always have running
47             min_spare_servers => 2, # min num of servers just sitting there
48             max_spare_servers => 10, # max num of servers just sitting there
49             check_for_waiting => 10, # how often to see if children laying around
50             check_for_spawn => 30, # how often to see if more children are needed
51             min_child_ttl => 10, # min time between starting a child and killing one
52             };
53             $prop->{'min_servers'} = $prop->{'max_servers'}
54 1 50 33     25 if !!defined($prop->{'min_servers'}) && $d->{'min_servers'} > $prop->{'max_servers'};
55             $prop->{'max_spare_servers'} = $prop->{'max_servers'} - 1
56 1 50 33     8 if !defined($prop->{'max_spare_servers'}) && $d->{'max_spare_servers'} >= $prop->{'max_servers'};
57 1 50       3 if (! defined $prop->{'min_spare_servers'}) {
58 0 0       0 my $min = defined($prop->{'min_servers'}) ? $prop->{'min_servers'} : $d->{'min_servers'};
59 0 0       0 $prop->{'min_spare_servers'} = $min if $prop > $min;
60             }
61              
62 1         5 foreach (keys %$d){
63 6 100 66     46 $prop->{$_} = $d->{$_} if !defined($prop->{$_}) || $prop->{$_} !~ /^\d+(?:\.\d+)?$/;
64             }
65              
66 1 50       5 if( $prop->{'max_spare_servers'} >= $prop->{'max_servers'} ){
67 0         0 $self->fatal("Error: \"max_spare_servers\" must be less than \"max_servers\"");
68             }
69              
70 1 50       8 if ($prop->{'min_spare_servers'}) {
71             $self->fatal("Error: \"min_spare_servers\" ($prop->{'min_spare_servers'}) must be less than \"$_\" ($prop->{$_})")
72 0         0 for grep {$prop->{'min_spare_servers'} > $prop->{$_}} qw(min_servers max_spare_servers);
  0         0  
73             }
74             }
75              
76              
77             sub loop {
78 1     1 1 5 my $self = shift;
79 1         2 my $prop = $self->{'server'};
80              
81 1         36 pipe(my $read, my $write); # get ready for child->parent communication
82 1         14 $read->autoflush(1);
83 1         65 $write->autoflush(1);
84 1         29 $prop->{'_READ'} = $read;
85 1         4 $prop->{'_WRITE'} = $write;
86              
87             # get ready for children
88 1         17 $prop->{'child_select'} = IO::Select->new($read);
89 1         90 $prop->{'children'} = {};
90 1         4 $prop->{'reaped_children'} = {};
91 1 50       7 if ($ENV{'HUP_CHILDREN'}) {
92 0         0 foreach my $line (split /\n/, $ENV{'HUP_CHILDREN'}) {
93 0 0       0 my ($pid, $status) = ($line =~ /^(\d+)\t(\w+)$/) ? ($1, $2) : next;
94 0         0 $prop->{'children'}->{$pid} = {status => $status, hup => 1};
95             }
96             }
97              
98             $prop->{'tally'} = {
99             time => time(),
100 0         0 waiting => scalar(grep {$_->{'status'} eq 'waiting'} values %{ $prop->{'children'} }),
  1         6  
101 0         0 processing => scalar(grep {$_->{'status'} eq 'processing'} values %{ $prop->{'children'} }),
  1         3  
102 1         18 dequeue => scalar(grep {$_->{'status'} eq 'dequeue'} values %{ $prop->{'children'} }),
  0         0  
  1         27  
103             };
104              
105 1         6 my $start = $prop->{'min_servers'};
106 1         7 $self->log(3, "Beginning prefork ($start processes)");
107 1         21 $self->run_n_children($start);
108              
109 1         70 $self->run_parent;
110             }
111              
112              
113             sub kill_n_children {
114 0     0 0 0 my ($self, $n) = @_;
115 0         0 my $prop = $self->{'server'};
116 0 0       0 return unless $n > 0;
117              
118 0         0 my $time = time;
119 0 0       0 return unless $time - $prop->{'last_kill'} > 10;
120 0         0 $prop->{'last_kill'} = $time;
121              
122 0         0 $self->log(3, "Killing \"$n\" children");
123              
124 0         0 foreach my $pid (keys %{ $prop->{'children'} }){
  0         0  
125             # Only kill waiting children
126             # XXX: This is race condition prone as the child may have
127             # started handling a connection, but will have to do for now
128 0         0 my $child = $prop->{'children'}->{$pid};
129 0 0       0 next if $child->{'status'} ne 'waiting';
130              
131 0         0 $n--;
132              
133 0 0       0 if (! kill('HUP', $pid)) {
134 0         0 $self->delete_child($pid);
135             }
136              
137 0 0       0 last if $n <= 0;
138             }
139             }
140              
141             sub run_n_children {
142 1     1 0 4 my ($self, $n) = @_;
143 1         3 my $prop = $self->{'server'};
144 1 50       12 return unless $n > 0;
145              
146 1         9 $self->run_n_children_hook($n);
147              
148 1         3 my ($parentsock, $childsock);
149 1         7 $self->log(3, "Starting \"$n\" children");
150 1         13 $prop->{'last_start'} = time();
151              
152 1         6 for (1 .. $n) {
153              
154 1 50       5 if ($prop->{'child_communication'}) {
155 1         11 require IO::Socket::UNIX;
156 1         50 ($parentsock, $childsock) = IO::Socket::UNIX->socketpair(IO::Socket::AF_UNIX, IO::Socket::SOCK_STREAM, IO::Socket::PF_UNSPEC);
157             }
158              
159 1         351 $self->pre_fork_hook;
160 1         33 local $!;
161 1         939 my $pid = fork;
162 1 50       72 if (! defined $pid) {
163 0 0       0 if ($prop->{'child_communication'}) {
164 0         0 $parentsock->close();
165 0         0 $childsock->close();
166             }
167 0         0 $self->fatal("Bad fork [$!]");
168             }
169              
170 1 50       36 if ($pid) { # parent
171 1 50       28 if( $prop->{'child_communication'} ){
172 1         65 $prop->{'child_select'}->add($parentsock);
173 1         271 $prop->{'children'}->{$pid}->{'sock'} = $parentsock;
174             }
175              
176 1         26 $prop->{'children'}->{$pid}->{'status'} = 'waiting';
177 1         166 $prop->{'tally'}->{'waiting'} ++;
178              
179             } else { # child
180 0 0       0 if ($prop->{'child_communication'}) {
181 0         0 $prop->{'parent_sock'} = $childsock;
182             }
183 0         0 $self->run_child;
184             }
185             }
186             }
187              
188       1 1   sub run_n_children_hook {}
189              
190             sub run_child {
191 0     0 0 0 my $self = shift;
192 0         0 my $prop = $self->{'server'};
193              
194             $SIG{'INT'} = $SIG{'TERM'} = $SIG{'QUIT'} = sub {
195 0     0   0 $self->child_finish_hook;
196 0         0 exit;
197 0         0 };
198 0         0 $SIG{'PIPE'} = 'IGNORE';
199 0         0 $SIG{'CHLD'} = 'DEFAULT';
200             $SIG{'HUP'} = sub {
201 0 0   0   0 if (! $prop->{'connected'}) {
202 0         0 $self->child_finish_hook;
203 0         0 exit;
204             }
205 0         0 $prop->{'SigHUPed'} = 1;
206 0         0 };
207              
208             # Open in child at start
209 0 0       0 if ($prop->{'serialize'} eq 'flock') {
210 0 0       0 open $prop->{'lock_fh'}, ">", $prop->{'lock_file'}
211             or $self->fatal("Couldn't open lock file \"$prop->{'lock_file'}\"[$!]");
212             }
213              
214 0         0 $self->log(4, "Child Preforked ($$)");
215              
216 0         0 delete @{ $prop }{qw(children tally last_start last_process)};
  0         0  
217              
218 0         0 $self->child_init_hook;
219 0         0 my $write = $prop->{'_WRITE'};
220              
221 0         0 while ($self->accept()) {
222 0         0 $prop->{'connected'} = 1;
223 0         0 print $write "$$ processing\n";
224              
225 0         0 my $ok = eval { $self->run_client_connection; 1 };
  0         0  
  0         0  
226 0 0       0 if (! $ok) {
227 0         0 print $write "$$ exiting\n";
228 0         0 die $@;
229             }
230              
231 0 0       0 last if $self->done;
232              
233 0         0 $prop->{'connected'} = 0;
234 0         0 print $write "$$ waiting\n";
235             }
236              
237 0         0 $self->child_finish_hook;
238              
239 0         0 print $write "$$ exiting\n";
240 0         0 exit;
241             }
242              
243              
244             sub run_parent {
245 1     1 0 11 my $self = shift;
246 1         13 my $prop = $self->{'server'};
247 1         9 my $id;
248              
249 1         35 $self->log(4, "Parent ready for children.");
250 1         4 my $read_fh = $prop->{'_READ'};
251              
252 1         23 @{ $prop }{qw(last_checked_for_dead last_checked_for_waiting last_checked_for_dequeue last_process last_kill)} = (time) x 5;
  1         28  
253              
254             my $reaper = sub {
255 0     0   0 while ( defined( my $chld = waitpid( -1, WNOHANG ) ) ) {
256 0 0       0 last unless $chld > 0;
257 0         0 $self->{'reaped_children'}->{$chld} = $?
258             ; # We'll deal with this in coordinate_children to avoid a race
259             }
260 1         22 };
261              
262             register_sig(
263             PIPE => 'IGNORE',
264 1     1   36 INT => sub { $self->server_close() },
265 0     0   0 TERM => sub { $self->server_close() },
266 0     0   0 HUP => sub { $self->sig_hup() },
267             CHLD => $reaper,
268 0     0   0 QUIT => sub { $self->{'server'}->{'kind_quit'} = 1; $self->server_close() },
  0         0  
269 0     0   0 TTIN => sub { $self->{'server'}->{$_}++ for qw(min_servers max_servers); $self->log(3, "Increasing server count ($self->{'server'}->{'max_servers'})") },
  0         0  
270 0     0   0 TTOU => sub { $self->{'server'}->{$_}-- for qw(min_servers max_servers); $self->log(3, "Decreasing server count ($self->{'server'}->{'max_servers'})") },
  0         0  
271 1         91 );
272              
273 1         22 $self->register_sig_pass;
274              
275 1 50       5 if ($ENV{'HUP_CHILDREN'}) {
276 0         0 $reaper->();
277             }
278              
279 1         3 while (1) {
280             ### Wait to read.
281             ## Normally it is not good to do selects with
282             ## getline or <$fh> but this is controlled output
283             ## where everything that comes through came from us.
284 4         43 my @fh = $prop->{'child_select'}->can_read($prop->{'check_for_waiting'});
285 4 50       4468 if (check_sigs()) {
286 0 0       0 last if $prop->{'_HUP'};
287             }
288              
289 3         62 $self->idle_loop_hook(\@fh);
290              
291 3 50       12 if (! @fh) {
292 0         0 $self->coordinate_children();
293 0         0 next;
294             }
295              
296 3         16 foreach my $fh (@fh) {
297 3 50       13 if ($fh != $read_fh) { # preforking server data
298 0         0 $self->child_is_talking_hook($fh);
299 0         0 next;
300             }
301              
302 3         71 my $line = <$fh>;
303 3 50       13 next if ! defined $line;
304              
305 3 50       16 last if $self->parent_read_hook($line); # optional test by user hook
306              
307             # child should say "$pid status\n"
308 3 50       54 next if $line !~ /^(\d+)\ +(waiting|processing|dequeue|exiting)$/;
309 3         52 my ($pid, $status) = ($1, $2);
310              
311 3 50       29 if (my $child = $prop->{'children'}->{$pid}) {
312 3 50       11 if ($status eq 'exiting') {
313 0         0 $self->delete_child($pid);
314              
315             } else {
316             # Decrement tally of state pid was in (plus sanity check)
317 3   33     28 my $old_status = $child->{'status'} || $self->log(2, "No status for $pid when changing to $status");
318 3 50       14 --$prop->{'tally'}->{$old_status} >= 0 || $self->log(2, "Tally for $status < 0 changing pid $pid from $old_status to $status");
319              
320 3         8 $child->{'status'} = $status;
321 3         8 ++$prop->{'tally'}->{$status};
322              
323 3 100       29 $prop->{'last_process'} = time() if $status eq 'processing';
324             }
325             }
326             }
327 3         38 $self->coordinate_children();
328             }
329             }
330              
331             sub run_dequeue {
332 0     0 1 0 my $self = shift;
333 0         0 $self->SUPER::run_dequeue;
334 0         0 $self->{'server'}->{'tally'}->{'dequeue'}++;
335             }
336              
337 0     0 1 0 sub cleanup_dead_child_hook { return; }
338              
339             sub coordinate_children {
340 3     3 0 7 my $self = shift;
341 3         8 my $prop = $self->{'server'};
342 3         8 my $time = time();
343              
344             # deleted SIG{'CHLD'} reaped children
345 3         8 foreach my $pid (keys %{ $self->{'reaped_children'} }) {
  3         31  
346 0         0 my $exit = delete $self->{'reaped_children'}->{$pid}; # delete each pid one by one to avoid another race
347 0 0       0 next if ! $prop->{'children'}->{$pid};
348 0         0 $self->delete_child($pid, $exit);
349             }
350              
351             # re-tally the possible types (only twice a minute)
352             # this might not be even necessary but is a nice sanity check
353 3   50     14 my $tally = $prop->{'tally'} ||= {};
354 3 50       15 if ($time - $tally->{'time'} > $prop->{'check_for_spawn'}) {
355 0         0 my $w = $tally->{'waiting'};
356 0         0 my $p = $tally->{'processing'};
357 0         0 $tally = $prop->{'tally'} = {
358             time => $time,
359             waiting => 0,
360             processing => 0,
361             dequeue => 0,
362             };
363 0         0 foreach (values %{ $prop->{'children'} }) {
  0         0  
364 0         0 $tally->{$_->{'status'}}++;
365             }
366 0         0 $w -= $tally->{'waiting'};
367 0         0 $p -= $tally->{'processing'};
368 0 0 0     0 $self->log(3, "Processing diff ($p), Waiting diff ($w)") if $p || $w;
369             }
370              
371 3         8 my $total = $tally->{'waiting'} + $tally->{'processing'};
372              
373 3 50 33     24 if ($total < $prop->{'min_servers'}) {
    50          
374 0         0 $self->run_n_children($prop->{'min_servers'} - $total); # need more min_servers
375              
376             } elsif ($tally->{'waiting'} < $prop->{'min_spare_servers'}
377             && $total < $prop->{'max_servers'}) { # need more min_spare_servers (up to max_servers)
378 0         0 my $n1 = $prop->{'min_spare_servers'} - $tally->{'waiting'};
379 0         0 my $n2 = $prop->{'max_servers'} - $total;
380 0 0       0 $self->run_n_children(($n2 > $n1) ? $n1 : $n2);
381             }
382              
383             # check to see if we should kill off some children
384 3 50       9 if ($time - $prop->{'last_checked_for_waiting'} > $prop->{'check_for_waiting'}) {
385 0         0 $prop->{'last_checked_for_waiting'} = $time;
386              
387             # need fewer max_spare_servers (down to min_servers)
388 0 0 0     0 if ($tally->{'waiting'} > $prop->{'max_spare_servers'}
    0          
389             && $total > $prop->{'min_servers'}) {
390              
391             ### see if we haven't started any in the last ten seconds
392 0 0       0 if ($time - $prop->{'last_start'} > $prop->{'min_child_ttl'}) {
393 0         0 my $n1 = $tally->{'waiting'} - $prop->{'max_spare_servers'};
394 0         0 my $n2 = $total - $prop->{'min_servers'};
395 0 0       0 $self->kill_n_children(($n2 > $n1) ? $n1 : $n2);
396             }
397              
398             } elsif ($total > $prop->{'max_servers'}) { # how did this happen?
399 0         0 $self->kill_n_children($total - $prop->{'max_servers'});
400             }
401             }
402              
403             # periodically make sure children are alive
404 3 50       17 if ($time - $prop->{'last_checked_for_dead'} > $prop->{'check_for_dead'}) {
405 0         0 $prop->{'last_checked_for_dead'} = $time;
406 0         0 foreach my $pid (keys %{ $prop->{'children'} }) {
  0         0  
407 0 0       0 if( ! kill(0, $pid) ) {
408 0         0 $self->cleanup_dead_child_hook( $prop->{'children'}->{$pid} );
409 0         0 $self->delete_child($pid);
410             }
411             }
412             }
413              
414             # take us down to min if we haven't had a request in a while
415 3 50 33     14 if ($time - $prop->{'last_process'} > 30 && $tally->{'waiting'} > $prop->{'min_spare_servers'}) {
416 0         0 my $n1 = $tally->{'waiting'} - $prop->{'min_spare_servers'};
417 0         0 my $n2 = $total - $prop->{'min_servers'};
418 0 0       0 $self->kill_n_children( ($n2 > $n1) ? $n1 : $n2 );
419             }
420              
421             # periodically check to see if we should clear the queue
422 3 50       12 if (defined $prop->{'check_for_dequeue'}) {
423 0 0       0 if ($time - $prop->{'last_checked_for_dequeue'} > $prop->{'check_for_dequeue'}) {
424 0         0 $prop->{'last_checked_for_dequeue'} = $time;
425 0 0 0     0 if (defined($prop->{'max_dequeue'})
426             && $tally->{'dequeue'} < $prop->{'max_dequeue'}) {
427 0         0 $self->run_dequeue();
428             }
429             }
430             }
431             }
432              
433             ### delete_child and other modifications contributed by Rob Mueller
434             sub delete_child {
435 1     1 0 5 my ($self, $pid, $exit) = @_;
436 1         4 my $prop = $self->{'server'};
437              
438 1         3 my $child = $prop->{'children'}->{$pid};
439 1 50       22 if (! $child) {
440 0         0 $self->log(2, "Attempt to delete already deleted child $pid");
441 0         0 return;
442             }
443              
444 1 50       6 return if ! exists $prop->{'children'}->{$pid}; # Already gone?
445              
446             # This means there was some sort of abnormal exit for the child, like a
447             # segfault.
448 1 50       11 if ($exit) {
449 0         0 my $status = $exit >> 8;
450 0         0 my $signal = $exit & 127;
451 0         0 my $message = "Child process $pid exited with status $status";
452 0 0       0 $message .= " - signal was $signal"
453             if $signal;
454              
455 0         0 $self->log(1, $message);
456             }
457              
458 1   33     6 my $status = $child->{'status'} || $self->log(2, "No status for $pid when deleting child");
459 1 50       6 --$prop->{'tally'}->{$status} >= 0 || $self->log(2, "Tally for $status < 0 deleting pid $pid");
460 1 50       4 $prop->{'tally'}->{'time'} = 0 if $child->{'hup'};
461              
462 1         19 $self->SUPER::delete_child($pid);
463             }
464              
465       3 1   sub parent_read_hook {}
466              
467       0 1   sub child_is_talking_hook {}
468              
469             1;
470              
471             __END__