File Coverage

blib/lib/Net/Server/Fork.pm
Criterion Covered Total %
statement 67 128 52.3
branch 12 46 26.0
condition 1 2 50.0
subroutine 13 20 65.0
pod 4 8 50.0
total 97 204 47.5


line stmt bran cond sub pod time code
1             # -*- perl -*-
2             #
3             # Net::Server::Fork - 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::Fork;
19              
20 2     2   14236 use strict;
  2         14  
  2         92  
21 2     2   26 use base qw(Net::Server);
  2         12  
  2         1300  
22 2     2   820 use Net::Server::SIG qw(register_sig check_sigs);
  2         4  
  2         114  
23 2     2   12 use Socket qw(SO_TYPE SOL_SOCKET SOCK_DGRAM);
  2         6  
  2         136  
24 2     2   10 use POSIX qw(WNOHANG);
  2         4  
  2         12  
25              
26 1     1 0 2 sub net_server_type { __PACKAGE__ }
27              
28             sub options {
29 1     1 0 4 my $self = shift;
30 1         14 my $ref = $self->SUPER::options(@_);
31 1         3 my $prop = $self->{'server'};
32 1         16 $ref->{$_} = \$prop->{$_} for qw(max_servers max_dequeue check_for_dead check_for_dequeue);
33 1         14 $ref->{'sig_passthrough'} = $prop->{'sig_passthrough'} = [];
34 1         4 return $ref;
35             }
36              
37             sub post_configure {
38 1     1 1 2 my $self = shift;
39 1         1 my $prop = $self->{'server'};
40 1         12 $self->SUPER::post_configure(@_);
41              
42 1 50       3 $prop->{'max_servers'} = 256 if ! defined $prop->{'max_servers'};
43 1 50       3 $prop->{'check_for_dead'} = 60 if ! defined $prop->{'check_for_dead'};
44              
45 1         19 $prop->{'ppid'} = $$;
46 1         9 $prop->{'multi_port'} = 1;
47             }
48              
49             sub loop {
50 1     1 1 2 my $self = shift;
51 1         2 my $prop = $self->{'server'};
52              
53 1         9 $prop->{'children'} = {};
54 1 50       7 if ($ENV{'HUP_CHILDREN'}) {
55 0         0 my %children = map {/^(\w+)$/; $1} split(/\s+/, $ENV{'HUP_CHILDREN'});
  0         0  
  0         0  
56 0         0 $children{$_} = {status => $children{$_}, hup => 1} foreach keys %children;
57 0         0 $prop->{'children'} = \%children;
58             }
59              
60             # register some of the signals for safe handling
61             register_sig(
62             PIPE => 'IGNORE',
63 1     1   25 INT => sub { $self->server_close() },
64 0     0   0 TERM => sub { $self->server_close() },
65 0     0   0 HUP => sub { $self->sig_hup() },
66             CHLD => sub {
67 0     0   0 while (defined(my $chld = waitpid(-1, WNOHANG))) {
68 0 0       0 last if $chld <= 0;
69 0         0 $self->delete_child($chld);
70             }
71             },
72 0     0   0 QUIT => sub { $self->{'server'}->{'kind_quit'} = 1; $self->server_close() },
  0         0  
73 0     0   0 TTIN => sub { $self->{'server'}->{'max_servers'}++; $self->log(3, "Increasing max server count ($self->{'server'}->{'max_servers'})") },
  0         0  
74 0     0   0 TTOU => sub { $self->{'server'}->{'max_servers'}--; $self->log(3, "Decreasing max server count ($self->{'server'}->{'max_servers'})") },
  0         0  
75 1         67 );
76              
77 1         16 $self->register_sig_pass;
78              
79 1 50       9 if ($ENV{'HUP_CHILDREN'}) {
80 0         0 while (defined(my $chld = waitpid(-1, WNOHANG))) {
81 0 0       0 last unless $chld > 0;
82 0         0 $self->delete_child($chld);
83             }
84             }
85              
86 1         4 my ($last_checked_for_dead, $last_checked_for_dequeue) = (time(), time());
87              
88 1         2 while (1) {
89              
90             ### make sure we don't use too many processes
91 2         7 my $n_children = grep { $_->{'status'} !~ /dequeue/ } values %{ $prop->{'children'} };
  1         20  
  2         33  
92 2         8 while ($n_children > $prop->{'max_servers'}){
93              
94 0         0 select(undef, undef, undef, 5); # block for a moment (don't look too often)
95 0         0 check_sigs();
96              
97 0         0 my $time = time();
98 0 0       0 if ($time - $last_checked_for_dead > $prop->{'check_for_dead'}) {
99 0         0 $last_checked_for_dead = $time;
100 0         0 $self->log(2, "Max number of children reached ($prop->{max_servers}) -- checking for alive.");
101 0         0 foreach (keys %{ $prop->{'children'} }){
  0         0  
102 0 0       0 kill(0,$_) or $self->delete_child($_);
103             }
104             }
105 0         0 $n_children = grep { $_->{'status'} !~ /dequeue/ } values %{ $prop->{'children'} };
  0         0  
  0         0  
106             }
107              
108 2 50       78 if ($prop->{'check_for_dequeue'}) {
109 0         0 my $time = time();
110 0 0       0 if ($time - $last_checked_for_dequeue > $prop->{'check_for_dequeue'}) {
111 0         0 $last_checked_for_dequeue = $time;
112 0 0       0 if ($prop->{'max_dequeue'}) {
113 0         0 my $n_dequeue = grep { $_->{'status'} =~ /dequeue/ } values %{ $prop->{'children'} };
  0         0  
  0         0  
114 0 0       0 $self->run_dequeue() if $n_dequeue < $prop->{'max_dequeue'};
115             }
116             }
117             }
118              
119 2         26 $self->pre_accept_hook;
120              
121 2 50       25 if (! $self->accept()) {
122 0 0       0 last if $prop->{'_HUP'};
123 0 0       0 last if $prop->{'done'};
124 0         0 next;
125             }
126              
127 1         25 $self->pre_fork_hook;
128              
129             ### fork a child so the parent can go back to listening
130 1         19 local $!;
131 1         834 my $pid = fork;
132 1 50       49 if (! defined $pid) {
133 0         0 $self->log(1, "Bad fork [$!]");
134 0         0 sleep 5;
135 0         0 next;
136             }
137              
138             # child
139 1 50       21 if (! $pid) {
140 0         0 $self->run_client_connection;
141 0         0 exit;
142             }
143              
144             # parent
145 1 50       48 close($prop->{'client'}) if !$prop->{'udp_true'};
146 1         37 $prop->{'children'}->{$pid}->{'status'} = 'processing';
147 1         99 $self->register_child($pid, 'fork');
148             }
149             }
150              
151       2 1   sub pre_accept_hook {};
152              
153             sub accept {
154 2     2 0 164 my ($self, $class) = @_;
155 2         3 my $prop = $self->{'server'};
156              
157             # block on trying to get a handle (select created because we specified multi_port)
158 2         39 my @socks = $prop->{'select'}->can_read(2);
159 2 50       880 if (check_sigs()) {
160 0 0       0 return undef if $prop->{'_HUP'};
161 0 0       0 return undef if ! @socks; # don't continue unless we have a connection
162             }
163              
164 1         42 my $sock = $socks[rand @socks];
165 1 50       6 return undef if ! defined $sock;
166              
167             # check if this is UDP
168 1 50       9 if (SOCK_DGRAM == $sock->getsockopt(SOL_SOCKET, SO_TYPE)) {
169 0         0 $prop->{'udp_true'} = 1;
170 0         0 $prop->{'client'} = $sock;
171 0         0 $prop->{'udp_peer'} = $sock->recv($prop->{'udp_data'}, $sock->NS_recv_len, $sock->NS_recv_flags);
172              
173             # Receive a SOCK_STREAM (TCP or UNIX) packet
174             } else {
175 1         30 delete $prop->{'udp_true'};
176 1   50     4 $prop->{'client'} = $sock->accept($class) || return;
177             }
178             }
179              
180             sub run_client_connection {
181 0     0 1 0 my $self = shift;
182              
183             ### close the main sock, we still have
184             ### the client handle, this will allow us
185             ### to HUP the parent at any time
186 0         0 $_ = undef foreach @{ $self->{'server'}->{'sock'} };
  0         0  
187              
188             ### restore sigs (for the child)
189 0         0 $SIG{'HUP'} = $SIG{'CHLD'} = $SIG{'INT'} = $SIG{'TERM'} = $SIG{'QUIT'} = 'DEFAULT';
190 0         0 $SIG{'PIPE'} = 'IGNORE';
191              
192 0         0 delete $self->{'server'}->{'children'};
193              
194 0         0 $self->child_init_hook;
195              
196 0         0 $self->SUPER::run_client_connection;
197              
198 0         0 $self->child_finish_hook;
199             }
200              
201             sub close_children {
202 1     1 0 3 my $self = shift;
203 1         13 $self->SUPER::close_children(@_);
204              
205 1         11 check_sigs(); # since we have captured signals - make sure we handle them
206              
207 1         7 register_sig(PIPE => 'DEFAULT',
208             INT => 'DEFAULT',
209             TERM => 'DEFAULT',
210             QUIT => 'DEFAULT',
211             HUP => 'DEFAULT',
212             CHLD => 'DEFAULT',
213             TTIN => 'DEFAULT',
214             TTOU => 'DEFAULT',
215             );
216             }
217              
218             1;
219              
220             __END__