File Coverage

blib/lib/Net/Server/PreForkSimple.pm
Criterion Covered Total %
statement 114 201 56.7
branch 24 94 25.5
condition 5 18 27.7
subroutine 21 29 72.4
pod 5 14 35.7
total 169 356 47.4


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             #
3             # Net::Server::PreForkSimple - Net::Server personality
4             #
5             # Copyright (C) 2001-2022
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::PreForkSimple;
19              
20 5     5   23178 use strict;
  5         31  
  5         161  
21 5     5   37 use base qw(Net::Server);
  5         74  
  5         3088  
22 5     5   3169 use File::Temp qw(tempfile);
  5         45396  
  5         297  
23 5     5   2126 use Net::Server::SIG qw(register_sig check_sigs);
  5         10  
  5         258  
24 5     5   28 use POSIX qw(WNOHANG EINTR);
  5         10  
  5         33  
25 5     5   503 use Fcntl ();
  5         7  
  5         10475  
26              
27 2     2 0 6 sub net_server_type { __PACKAGE__ }
28              
29             sub options {
30 3     3 0 11 my $self = shift;
31 3         92 my $ref = $self->SUPER::options(@_);
32 3         10 my $prop = $self->{'server'};
33              
34 3         136 $ref->{$_} = \$prop->{$_} for qw(max_servers max_requests max_dequeue
35             check_for_dead check_for_dequeue
36             lock_file serialize);
37 3         30 $ref->{'sig_passthrough'} = $prop->{'sig_passthrough'} = [];
38 3         11 return $ref;
39             }
40              
41             sub post_configure {
42 3     3 1 8 my $self = shift;
43 3         6 my $prop = $self->{'server'};
44 3         46 $self->SUPER::post_configure;
45              
46             ### some default values to check for
47 3         12 my $d = {
48             max_servers => 50, # max num of servers to run
49             max_requests => 1000, # num of requests for each child to handle
50             check_for_dead => 30, # how often to see if children are alive
51             };
52 3         11 foreach (keys %$d){
53             $prop->{$_} = $d->{$_}
54 9 100 66     90 unless defined($prop->{$_}) && $prop->{$_} =~ /^\d+$/;
55             }
56              
57 3         74 $prop->{'ppid'} = $$;
58             }
59              
60              
61             sub post_bind {
62 3     3 1 19 my $self = shift;
63 3         9 my $prop = $self->{'server'};
64 3         31 $self->SUPER::post_bind;
65              
66 3 50 33     45 if ($prop->{'multi_port'} && $prop->{'serialize'} && $prop->{'serialize'} eq 'none') {
      33        
67 0         0 $self->log(2, "Passed serialize value of none is incompatible with multiple ports - using default serialize");
68 0         0 delete $prop->{'serialize'};
69             }
70 3 50 33     15 if (!$prop->{'serialize'}
71             || $prop->{'serialize'} !~ /^(flock|semaphore|pipe|none)$/i) {
72 3 50       14 $prop->{'serialize'} = ($^O eq 'MSWin32') ? 'pipe' : 'flock';
73             }
74 3         13 $prop->{'serialize'} =~ tr/A-Z/a-z/;
75              
76 3 50       11 if ($prop->{'serialize'} eq 'flock') {
    0          
    0          
    0          
77 3         13 $self->log(3, "Setting up serialization via flock");
78 3 50       12 if (defined $prop->{'lock_file'}) {
79 0         0 $prop->{'lock_file_unlink'} = undef;
80             } else {
81 3         78 (my $fh, $prop->{'lock_file'}) = tempfile();
82             # We don't need to keep the file handle open in the parent;
83             # each child opens it separately to avoid sharing the lock
84 3 50       2933 close $fh or die "Cannot close lock file $prop->{'lock_file'}: $!";
85 3         22 $prop->{'lock_file_unlink'} = 1;
86             }
87              
88             } elsif ($prop->{'serialize'} eq 'semaphore') {
89 0         0 $self->log(3, "Setting up serialization via semaphore");
90 0         0 require IPC::SysV;
91 0         0 require IPC::Semaphore;
92 0 0       0 my $s = IPC::Semaphore->new(IPC::SysV::IPC_PRIVATE(), 1, IPC::SysV::S_IRWXU() | IPC::SysV::IPC_CREAT())
93             or $self->fatal("Semaphore error [$!]");
94 0 0       0 $s->setall(1) or $self->fatal("Semaphore create error [$!]");
95 0         0 $prop->{'sem'} = $s;
96              
97             } elsif ($prop->{'serialize'} eq 'pipe') {
98 0         0 $self->log(3, "Setting up serialization via pipe");
99 0         0 pipe(my $waiting, my $ready);
100 0         0 $ready->autoflush(1);
101 0         0 $waiting->autoflush(1);
102 0         0 $prop->{'_READY'} = $ready;
103 0         0 $prop->{'_WAITING'} = $waiting;
104 0         0 print $ready "First\n";
105             } elsif ($prop->{'serialize'} eq 'none') {
106 0         0 $self->log(3, "Using no serialization");
107             } else {
108 0         0 $self->fatal("Unknown serialization type \"$prop->{'serialize'}\"");
109             }
110              
111             }
112              
113             sub loop {
114 2     2 1 6 my $self = shift;
115 2         4 my $prop = $self->{'server'};
116              
117 2         22 $prop->{'children'} = {};
118 2 50       10 if ($ENV{'HUP_CHILDREN'}) {
119 0         0 my %children = map {/^(\w+)$/; $1} split(/\s+/, $ENV{'HUP_CHILDREN'});
  0         0  
  0         0  
120 0         0 $children{$_} = {status => $children{$_}, hup => 1} foreach keys %children;
121 0         0 $prop->{'children'} = \%children;
122             }
123              
124 2         26 $self->log(3, "Beginning prefork ($prop->{'max_servers'} processes)");
125              
126 2         16 $self->run_n_children($prop->{'max_servers'});
127              
128 1         79 $self->run_parent;
129              
130             }
131              
132             sub run_n_children {
133 2     2 0 6 my ($self, $n) = @_;
134 2 50       8 return if $n <= 0;
135 2         4 my $prop = $self->{'server'};
136              
137 2         26 $self->run_n_children_hook;
138              
139 2         8 $self->log(3, "Starting \"$n\" children");
140              
141 2         6 for (1 .. $n) {
142 3         51 $self->pre_fork_hook;
143 3         138 local $!;
144 3         18147 my $pid = fork;
145 3 50       111 $self->fatal("Bad fork [$!]") if ! defined $pid;
146              
147 3 100       72 if ($pid) {
148 2         146 $prop->{'children'}->{$pid}->{'status'} = 'processing';
149 2         123 $self->register_child($pid, 'preforksimple');
150             } else {
151 1         63 $self->run_child;
152             }
153             }
154             }
155              
156       2 1   sub run_n_children_hook {}
157              
158             sub run_child {
159 1     1 0 8 my $self = shift;
160 1         8 my $prop = $self->{'server'};
161              
162             $SIG{'INT'} = $SIG{'TERM'} = $SIG{'QUIT'} = sub {
163 1     1   2084 $self->child_finish_hook;
164 1         234 exit;
165 1         527 };
166 1         67 $SIG{'PIPE'} = 'IGNORE';
167 1         12 $SIG{'CHLD'} = 'DEFAULT';
168             $SIG{'HUP'} = sub {
169 0 0   0   0 if (! $prop->{'connected'}) {
170 0         0 $self->child_finish_hook;
171 0         0 exit;
172             }
173 0         0 $prop->{'SigHUPed'} = 1;
174 1         23 };
175              
176 1 50       20 my $needs_lock = ($prop->{'serialize'} eq 'flock') ? 1 : 0;
177 1 50       12 if ($needs_lock) {
178 1 50       140 open($prop->{'lock_fh'}, ">", $prop->{'lock_file'})
179             or $self->fatal("Couldn't open lock file \"$prop->{'lock_file'}\"[$!]");
180             }
181              
182 1         58 $self->log(4, "Child Preforked ($$)");
183 1         12 delete $prop->{'children'};
184              
185 1         47 $self->child_init_hook;
186              
187 1         25 while ($self->accept()) {
188 1         11 $prop->{'connected'} = 1;
189 1         40 $self->run_client_connection;
190 1         3 $prop->{'connected'} = 0;
191 1 50       17 last if $self->done;
192             }
193              
194 0         0 $self->child_finish_hook;
195              
196 0 0 0     0 close($prop->{'lock_fh'}) if $needs_lock && $prop->{'lock_fh'};
197              
198 0         0 $self->log(4, "Child leaving ($prop->{'max_requests'})");
199 0         0 exit;
200              
201             }
202              
203 0     0 0 0 sub is_prefork { 1 }
204              
205             ### We can only let one process do the selecting at a time
206             ### this override makes sure that nobody else can do it
207             ### while we are. We do this either by opening a lock file
208             ### and getting an exclusive lock (this will block all others
209             ### until we release it) or by using semaphores to block
210             sub accept {
211 2     2 0 1115 my $self = shift;
212 2         4 my $prop = $self->{'server'};
213              
214 2 50       167 if ($prop->{'serialize'} eq 'flock') {
    0          
    0          
215 2         543 while (! flock $prop->{'lock_fh'}, Fcntl::LOCK_EX()) {
216 0 0       0 next if $! == EINTR;
217 0         0 $self->fatal("Couldn't get lock on file \"$prop->{'lock_file'}\" [$!]");
218             }
219 2         36 my $v = $self->SUPER::accept();
220 1         50 flock $prop->{'lock_fh'}, Fcntl::LOCK_UN();
221 1         6 return $v;
222             } elsif ($prop->{'serialize'} eq 'semaphore') {
223 0 0       0 $prop->{'sem'}->op(0, -1, IPC::SysV::SEM_UNDO()) or $self->fatal("Semaphore Error [$!]");
224 0         0 my $v = $self->SUPER::accept();
225 0 0       0 $prop->{'sem'}->op(0, 1, IPC::SysV::SEM_UNDO()) or $self->fatal("Semaphore Error [$!]");
226 0         0 return $v;
227             } elsif ($prop->{'serialize'} eq 'pipe') {
228 0         0 my $waiting = $prop->{'_WAITING'};
229 0         0 scalar <$waiting>; # read one line - kernel says who gets it
230 0         0 my $v = $self->SUPER::accept();
231 0         0 print { $prop->{'_READY'} } "Next!\n";
  0         0  
232 0         0 return $v;
233             } else {
234 0         0 my $v = $self->SUPER::accept();
235 0         0 return $v;
236             }
237             }
238              
239             sub done {
240 1     1 0 3 my $self = shift;
241 1         4 my $prop = $self->{'server'};
242 1 50       4 $prop->{'done'} = shift if @_;
243 1 50       3 return 1 if $prop->{'done'};
244 1 50       4 return 1 if $prop->{'requests'} >= $prop->{'max_requests'};
245 1 50       4 return 1 if $prop->{'SigHUPed'};
246 1 50       30 if (! kill 0, $prop->{'ppid'}) {
247 0         0 $self->log(3, "Parent process gone away. Shutting down");
248 0         0 return 1;
249             }
250             }
251              
252             sub run_parent {
253 1     1 0 15 my $self=shift;
254 1         14 my $prop = $self->{'server'};
255              
256 1         37 $self->log(4, "Parent ready for children.");
257              
258 1         17 $prop->{'last_checked_for_dead'} = $prop->{'last_checked_for_dequeue'} = time();
259              
260             register_sig(
261             PIPE => 'IGNORE',
262 1     1   28 INT => sub { $self->server_close() },
263 0     0   0 TERM => sub { $self->server_close() },
264 0     0   0 HUP => sub { $self->sig_hup() },
265             CHLD => sub {
266 0     0   0 while (defined(my $chld = waitpid(-1, WNOHANG))) {
267 0 0       0 last unless $chld > 0;
268 0         0 $self->delete_child($chld);
269             }
270             },
271 0     0   0 QUIT => sub { $self->{'server'}->{'kind_quit'} = 1; $self->server_close() },
  0         0  
272 0     0   0 TTIN => sub { $self->{'server'}->{'max_servers'}++; $self->log(3, "Increasing max server count ($self->{'server'}->{'max_servers'})") },
  0         0  
273             TTOU => sub {
274 0     0   0 $self->{'server'}->{'max_servers'}--;
275 0         0 $self->log(3, "Decreasing max server count ($self->{'server'}->{'max_servers'})");
276 0 0       0 if (defined(my $pid = each %{ $prop->{'children'} })) {
  0         0  
277 0 0       0 $self->delete_child($pid) if ! kill('HUP', $pid);
278             }
279             },
280 1         111 );
281              
282 1         27 $self->register_sig_pass;
283              
284 1 50       12 if ($ENV{'HUP_CHILDREN'}) {
285 0         0 while (defined(my $chld = waitpid(-1, WNOHANG))) {
286 0 0       0 last unless $chld > 0;
287 0         0 $self->delete_child($chld);
288             }
289             }
290              
291 1         3 while (1) {
292 1         5929 select undef, undef, undef, 10;
293              
294 1 0       36 if (check_sigs()){
295 0 0       0 last if $prop->{'_HUP'};
296             }
297              
298 0         0 $self->idle_loop_hook();
299              
300             # periodically make sure children are alive
301 0         0 my $time = time();
302 0 0       0 if ($time - $prop->{'last_checked_for_dead'} > $prop->{'check_for_dead'}) {
303 0         0 $prop->{'last_checked_for_dead'} = $time;
304 0         0 foreach (keys %{ $prop->{'children'} }) {
  0         0  
305 0 0       0 kill(0,$_) or $self->delete_child($_);
306             }
307             }
308              
309             # make sure we always have max_servers
310 0         0 my $total_n = 0;
311 0         0 my $total_d = 0;
312 0         0 foreach (values %{ $prop->{'children'} }){
  0         0  
313 0 0       0 if( $_->{'status'} eq 'dequeue' ){
314 0         0 $total_d ++;
315             }else{
316 0         0 $total_n ++;
317             }
318             }
319              
320 0 0       0 if( $prop->{'max_servers'} > $total_n ){
321 0         0 $self->run_n_children( $prop->{'max_servers'} - $total_n );
322             }
323              
324             # periodically check to see if we should clear the queue
325 0 0       0 if( defined $prop->{'check_for_dequeue'} ){
326 0 0       0 if( $time - $prop->{'last_checked_for_dequeue'}
327             > $prop->{'check_for_dequeue'} ){
328 0         0 $prop->{'last_checked_for_dequeue'} = $time;
329 0 0 0     0 if( defined($prop->{'max_dequeue'})
330             && $total_d < $prop->{'max_dequeue'} ){
331 0         0 $self->run_dequeue();
332             }
333             }
334             }
335              
336             }
337             }
338              
339       3 1   sub idle_loop_hook {}
340              
341             sub close_children {
342 2     2 0 5 my $self = shift;
343 2         41 $self->SUPER::close_children(@_);
344              
345 2         38 check_sigs(); # since we have captured signals - make sure we handle them
346              
347 2         48 register_sig(PIPE => 'DEFAULT',
348             INT => 'DEFAULT',
349             TERM => 'DEFAULT',
350             QUIT => 'DEFAULT',
351             HUP => 'DEFAULT',
352             CHLD => 'DEFAULT',
353             TTIN => 'DEFAULT',
354             TTOU => 'DEFAULT',
355             );
356             }
357              
358             1;
359              
360             __END__