1 package FS::Daemon::Preforking;
6 FS::Daemon::Preforking - A preforking web server
10 use FS::Daemon::Preforking qw( freeside_init1 freeside_init2 daemon_run );
12 my $me = 'mydaemon'; #keep unique among fs daemons, for logfiles etc.
14 freeside_init1($me); #daemonize, drop root and connect to freeside
16 #do setup tasks which should throw an error to the shell starting the daemon
18 freeside_init2($me); #move logging to logfile and disassociate from terminal
20 #do setup tasks which will warn/error to the log file, such as declining to
21 # run if our config is not in place
24 'port' => 5454, #keep unique among fs daemons
25 'handle_request' => \&handle_request,
29 my $request = shift; #HTTP::Request object
33 return $response; #HTTP::Response object
39 Based on L<http://www.perlmonks.org/?node_id=582781> by Justin Hawkins
41 and L<http://poe.perl.org/?POE_Cookbook/Web_Server_With_Forking>
48 use constant DEBUG => 0; # Enable much runtime information.
49 use constant MAX_PROCESSES => 10; # Total server process count. XXX conf to increase per-different daemon for busy sites using this (currently the only things using this are freeside-xmlrpcd and freeside-selfservice-xmlrpcd)
50 #use constant TESTING_CHURN => 0; # Randomly test process respawning.
52 use vars qw( @EXPORT_OK $FREESIDE_LOG $SERVER_PORT $user $handle_request );
53 @EXPORT_OK = qw( freeside_init1 freeside_init2 daemon_run );
54 $FREESIDE_LOG = '%%%FREESIDE_LOG%%%';
56 use POE 1.2; # Base features.
57 use POE::Filter::HTTPD; # For serving HTTP content.
58 use POE::Wheel::ReadWrite; # For socket I/O.
59 use POE::Wheel::SocketFactory; # For serving socket connections.
61 use FS::Daemon qw( daemonize1 drop_root logfile daemonize2 );
62 use FS::UID qw( adminsuidsetup forksuidsetup dbh );
64 #use FS::TicketSystem;
69 $user = shift @ARGV or die &usage($name);
71 $FS::Daemon::NOSIG = 1;
72 $FS::Daemon::PID_NEWSTYLE = 1;
75 POE::Kernel->has_forked(); #daemonize forks...
79 adminsuidsetup($user);
85 logfile("$FREESIDE_LOG/$name.log");
93 $SERVER_PORT = $opt{port};
94 $handle_request = $opt{handle_request};
96 #parent doesn't need to hold a DB connection open
101 server_spawn(MAX_PROCESSES);
107 ### Spawn the main server. This will run as the parent process.
110 my ($max_processes) = @_;
112 POE::Session->create(
114 _start => \&server_start,
115 _stop => \&server_stop,
116 do_fork => \&server_do_fork,
117 got_error => \&server_got_error,
118 got_sig_int => \&server_got_sig_int,
119 got_sig_child => \&server_got_sig_child,
120 got_connection => \&server_got_connection,
121 _child => sub { undef },
123 heap => { max_processes => MAX_PROCESSES },
127 ### The main server session has started. Set up the server socket and
128 ### bookkeeping information, then fork the initial child processes.
131 my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
133 $heap->{server} = POE::Wheel::SocketFactory->new
134 ( BindPort => $SERVER_PORT,
135 SuccessEvent => "got_connection",
136 FailureEvent => "got_error",
140 $kernel->sig( INT => "got_sig_int" );
141 $kernel->sig( TERM => "got_sig_int" ); #huh
143 $heap->{children} = {};
144 $heap->{is_a_child} = 0;
146 warn "Server $$ has begun listening on port $SERVER_PORT\n";
148 $kernel->yield("do_fork");
151 ### The server session has shut down. If this process has any
152 ### children, signal them to shutdown too.
156 DEBUG and warn "Server $$ stopped.\n";
158 if ( my @children = keys %{ $heap->{children} } ) {
159 DEBUG and warn "Server $$ is signaling children to stop.\n";
160 kill INT => @children;
164 ### The server session has encountered an error. Shut it down.
166 sub server_got_error {
167 my ( $heap, $syscall, $errno, $error ) = @_[ HEAP, ARG0 .. ARG2 ];
168 warn( "Server $$ got $syscall error $errno: $error\n",
169 "Server $$ is shutting down.\n",
171 delete $heap->{server};
174 ### The server has a need to fork off more children. Only honor that
175 ### request form the parent, otherwise we would surely "forkbomb".
176 ### Fork off as many child processes as we need.
179 my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
181 return if $heap->{is_a_child};
183 #my $current_children = keys %{ $heap->{children} };
184 #for ( $current_children + 2 .. $heap->{max_processes} ) {
185 while (scalar(keys %{$heap->{children}}) < $heap->{max_processes}) {
187 DEBUG and warn "Server $$ is attempting to fork.\n";
191 unless ( defined($pid) ) {
193 warn( "Server $$ fork failed: $!\n",
194 "Server $$ will retry fork shortly.\n",
196 $kernel->delay( do_fork => 1 );
200 # Parent. Add the child process to its list.
202 $heap->{children}->{$pid} = 1;
203 $kernel->sig_child($pid, "got_sig_child");
207 # Child. Clear the child process list.
208 $kernel->has_forked();
209 DEBUG and warn "Server $$ forked successfully.\n";
210 $heap->{is_a_child} = 1;
211 $heap->{children} = {};
213 #freeside db connection, etc.
214 forksuidsetup($user);
216 #why isn't this needed ala freeside-selfservice-server??
217 #FS::TicketSystem->init();
223 ### The server session received SIGINT. Don't handle the signal,
224 ### which in turn will trigger the process to exit gracefully.
226 sub server_got_sig_int {
227 my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
228 DEBUG and warn "Server $$ received SIGINT/TERM.\n";
230 if ( my @children = keys %{ $heap->{children} } ) {
231 DEBUG and warn "Server $$ is signaling children to stop.\n";
232 kill INT => @children;
235 delete $heap->{server};
236 $kernel->sig_handled();
239 ### The server session received a SIGCHLD, indicating that some child
240 ### server has gone away. Remove the child's process ID from our
241 ### list, and trigger more fork() calls to spawn new children.
243 sub server_got_sig_child {
244 my ( $kernel, $heap, $child_pid ) = @_[ KERNEL, HEAP, ARG1 ];
246 return unless delete $heap->{children}->{$child_pid};
248 DEBUG and warn "Server $$ reaped child $child_pid.\n";
249 $kernel->yield("do_fork") if exists $_[HEAP]->{server};
252 ### The server session received a connection request. Spawn off a
253 ### client handler session to parse the request and respond to it.
255 sub server_got_connection {
256 my ( $heap, $socket, $peer_addr, $peer_port ) = @_[ HEAP, ARG0, ARG1, ARG2 ];
258 DEBUG and warn "Server $$ received a connection.\n";
260 POE::Session->create(
262 _start => \&client_start,
263 _stop => \&client_stop,
264 got_request => \&client_got_request,
265 got_flush => \&client_flushed_request,
266 got_error => \&client_got_error,
267 _parent => sub { 0 },
271 peer_addr => $peer_addr,
272 peer_port => $peer_port,
276 # # Gracefully exit if testing process churn.
277 # delete $heap->{server}
278 # if TESTING_CHURN and $heap->{is_a_child} and ( rand() < 0.1 );
281 ### The client handler has started. Wrap its socket in a ReadWrite
282 ### wheel to begin interacting with it.
287 $heap->{client} = POE::Wheel::ReadWrite->new
288 ( Handle => $heap->{socket},
289 Filter => POE::Filter::HTTPD->new(),
290 InputEvent => "got_request",
291 ErrorEvent => "got_error",
292 FlushedEvent => "got_flush",
295 DEBUG and warn "Client handler $$/", $_[SESSION]->ID, " started.\n";
298 ### The client handler has stopped. Log that fact.
301 DEBUG and warn "Client handler $$/", $_[SESSION]->ID, " stopped.\n";
304 ### The client handler has received a request. If it's an
305 ### HTTP::Response object, it means some error has occurred while
306 ### parsing the request. Send that back and return immediately.
307 ### Otherwise parse and process the request, generating and sending an
308 ### HTTP::Response object in response.
310 sub client_got_request {
311 my ( $heap, $request ) = @_[ HEAP, ARG0 ];
314 warn "Client handler $$/", $_[SESSION]->ID, " is handling a request.\n";
316 if ( $request->isa("HTTP::Response") ) {
317 $heap->{client}->put($request);
321 forksuidsetup($user) unless dbh && dbh->ping;
323 my $response = &{ $handle_request }( $request );
325 $heap->{client}->put($response);
328 ### The client handler received an error. Stop the ReadWrite wheel,
329 ### which also closes the socket.
331 sub client_got_error {
332 my ( $heap, $operation, $errnum, $errstr ) = @_[ HEAP, ARG0, ARG1, ARG2 ];
334 warn( "Client handler $$/", $_[SESSION]->ID,
335 " got $operation error $errnum: $errstr\n",
336 "Client handler $$/", $_[SESSION]->ID, " is shutting down.\n"
338 delete $heap->{client};
341 ### The client handler has flushed its response to the socket. We're
342 ### done with the client connection, so stop the ReadWrite wheel.
344 sub client_flushed_request {
347 warn( "Client handler $$/", $_[SESSION]->ID,
348 " flushed its response.\n",
349 "Client handler $$/", $_[SESSION]->ID, " is shutting down.\n"
351 delete $heap->{client};
356 die "Usage:\n\n freeside-$name user\n";