import FCGI-ProcManager 0.16 from CPAN
[catagits/FCGI-ProcManager.git] / ProcManager.pm
CommitLineData
0baf5fac 1package FCGI::ProcManager;
2
3# Copyright (c) 2000, FundsXpress Financial Network, Inc.
4# This library is free software released under the GNU Lesser General
5# Public License, Version 2.1. Please read the important licensing and
6# disclaimer information included below.
7
c2bbadb3 8# $Id: ProcManager.pm,v 1.17 2001/02/09 16:15:47 muaddie Exp $
0baf5fac 9
10use strict;
4ceac1a1 11use Exporter;
4ceac1a1 12
518709ed 13use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS $Q);
0baf5fac 14BEGIN {
c2bbadb3 15 $VERSION = '0.16';
4ceac1a1 16 @ISA = qw(Exporter);
c2bbadb3 17 @EXPORT_OK = qw(pm_manage pm_die pm_wait
4ceac1a1 18 pm_write_pid_file pm_remove_pid_file
c146b63d 19 pm_pre_dispatch pm_post_dispatch
518709ed 20 pm_change_process_name pm_received_signal pm_parameter
21 pm_warn pm_notify pm_abort pm_exit);
4ceac1a1 22 $EXPORT_TAGS{all} = \@EXPORT_OK;
23 $FCGI::ProcManager::Default = 'FCGI::ProcManager';
0baf5fac 24}
25
26=head1 NAME
27
28 FCGI::ProcManager - functions for managing FastCGI applications.
29
30=head1 SYNOPSIS
31
518709ed 32{
33 # In Object-oriented style.
0baf5fac 34 use CGI::Fast;
35 use FCGI::ProcManager;
c2bbadb3 36 my $proc_manager = FCGI::ProcManager->new({
37 n_processes => 10
38 });
518709ed 39 $proc_manager->pm_manage();
0baf5fac 40 while (my $cgi = CGI::Fast->new()) {
518709ed 41 $proc_manager->pm_pre_dispatch();
42 # ... handle the request here ...
43 $proc_manager->pm_post_dispatch();
44 }
45
46 # This style is also supported:
47 use CGI::Fast;
c2bbadb3 48 use FCGI::ProcManager qw(pm_manage pm_pre_dispatch
49 pm_post_dispatch);
518709ed 50 pm_manage( n_processes => 10 );
51 while (my $cgi = CGI::Fast->new()) {
52 pm_pre_dispatch();
0baf5fac 53 #...
518709ed 54 pm_post_dispatch();
55 }
0baf5fac 56
57=head1 DESCRIPTION
58
518709ed 59FCGI::ProcManager is used to serve as a FastCGI process manager. By
60re-implementing it in perl, developers can more finely tune performance in
61their web applications, and can take advantage of copy-on-write semantics
62prevalent in UNIX kernel process management. The process manager should
63be invoked before the caller''s request loop
64
65The primary routine, C<pm_manage>, enters a loop in which it maintains a
66number of FastCGI servers (via fork(2)), and which reaps those servers
67when they die (via wait(2)).
68
69C<pm_manage> provides too hooks:
70
71 C<managing_init> - called just before the manager enters the manager loop.
72 C<handling_init> - called just before a server is returns from C<pm_manage>
73
74It is necessary for the caller, when implementing its request loop, to
75insert a call to C<pm_pre_dispatch> at the top of the loop, and then
767C<pm_post_dispatch> at the end of the loop.
77
0baf5fac 78
79=head1 METHODS
80
81=head2 new
82
c2bbadb3 83 class or instance
84 (ProcManager) new([hash parameters])
85
86Constructs a new process manager. Takes an option has of initial parameter
87values, and assigns these to the constructed object HASH, overriding any
88default values. The default parameter values currently are:
89
90 role => manager
91 start_delay => 0
92 die_timeout => 60
93
0baf5fac 94=cut
95
96sub new {
97 my ($proto,$init) = @_;
98
518709ed 99 my $this = {
100 role => "manager",
101 start_delay => 0,
102 die_timeout => 60
103 };
0baf5fac 104 $init and %$this = %$init;
4ceac1a1 105
106 bless $this, ref($proto)||$proto;
0baf5fac 107
108 $this->{PIDS} = {};
109
110 return $this;
111}
112
518709ed 113=head1 Manager methods
4ceac1a1 114
115=head2 pm_manage
0baf5fac 116
c2bbadb3 117 instance or export
118 (int) pm_manage([hash parameters])
0baf5fac 119
120DESCRIPTION:
121
c2bbadb3 122When this is called by a FastCGI script to manage application servers. It
123defines a sequence of instructions for a process to enter this method and
124begin forking off and managing those handlers, and it defines a sequence of
125instructions to intialize those handlers.
126
127If n_processes < 1, the managing section is subverted, and only the
128handling sequence is executed.
129
130Either returns the return value of pm_die() and/or pm_abort() (which will
131not ever return in general), or returns 1 to the calling script to begin
132handling requests.
0baf5fac 133
134=cut
135
4ceac1a1 136sub pm_manage {
518709ed 137 my ($this,%values) = self_or_default(@_);
138 map { $this->pm_parameter($_,$values{$_}) } keys %values;
0baf5fac 139
518709ed 140 # skip to handling now if we won't be managing any processes.
c146b63d 141 $this->n_processes() or goto HANDLING;
0baf5fac 142
518709ed 143 # call the (possibly overloaded) management initialization hook.
144 $this->role("manager");
c146b63d 145 $this->managing_init();
518709ed 146 $this->pm_notify("initialized");
0baf5fac 147
518709ed 148 my $manager_pid = $$;
0baf5fac 149
518709ed 150 MANAGING_LOOP: while (1) {
151
152 # if the calling process goes away, perform cleanup.
153 getppid() == 1 and
154 return $this->pm_die("calling process has died");
0baf5fac 155
518709ed 156 $this->n_processes() > 0 or
157 return $this->pm_die();
0baf5fac 158
518709ed 159 # while we have fewer servers than we want.
160 PIDS: while (keys(%{$this->{PIDS}}) < $this->n_processes()) {
161
162 if (my $pid = fork()) {
163 # the manager remembers the server.
0baf5fac 164 $this->{PIDS}->{$pid} = { pid=>$pid };
518709ed 165 $this->pm_notify("server (pid $pid) started");
0baf5fac 166
167 } elsif (! defined $pid) {
518709ed 168 return $this->pm_abort("fork: $!");
0baf5fac 169
170 } else {
518709ed 171 $this->role("server");
172 $this->{MANAGER_PID} = $manager_pid;
173 # the server exits the managing loop.
174 last MANAGING_LOOP;
0baf5fac 175 }
0baf5fac 176
518709ed 177 for (my $s = $this->start_delay(); $s; $s = sleep $s) {};
178 }
c146b63d 179
518709ed 180 # this should block until the next server dies.
c2bbadb3 181 $this->pm_wait();
0baf5fac 182
183 }# while 1
184
518709ed 185HANDLING:
0baf5fac 186
c2bbadb3 187 # forget any children we had been collecting.
188 delete $this->{PIDS};
189
518709ed 190 # call the (possibly overloaded) handling init hook
191 $this->role("server");
c146b63d 192 $this->handling_init();
518709ed 193 $this->pm_notify("initialized");
0baf5fac 194
518709ed 195 # server returns
0baf5fac 196 return 1;
197}
198
c146b63d 199=head2 managing_init
0baf5fac 200
c2bbadb3 201 instance
202 () managing_init()
203
204DESCRIPTION:
205
206Overrideable method which initializes a process manager. In order to
207handle signals, manage the PID file, and change the process name properly,
208any method which overrides this should call SUPER::managing_init().
209
0baf5fac 210=cut
211
c146b63d 212sub managing_init {
c2bbadb3 213 my ($this) = @_;
0baf5fac 214
518709ed 215 # begin to handle signals.
216 $SIG{TERM} = sub { $this->sig_manager(@_) };
217 $SIG{HUP} = sub { $this->sig_manager(@_) };
0baf5fac 218
518709ed 219 # change the name of this process as it appears in ps(1) output.
220 $this->pm_change_process_name("perl-fcgi-pm");
0baf5fac 221
518709ed 222 $this->pm_write_pid_file();
0baf5fac 223}
224
518709ed 225
226=head2 pm_die
0baf5fac 227
c2bbadb3 228 instance or export
229 () pm_die(string msg[, int exit_status])
230
231DESCRIPTION:
232
233This method is called when a process manager receives a notification to
234shut itself down. pm_die() attempts to shutdown the process manager
235gently, sending a SIGTERM to each managed process, waiting die_timeout()
236seconds to reap each process, and then exit gracefully once all children
237are reaped, or to abort if all children are not reaped.
238
0baf5fac 239=cut
240
518709ed 241sub pm_die {
242 my ($this,$msg,$n) = self_or_default(@_);
243
244 # stop handling signals.
245 $SIG{HUP} = 'DEFAULT';
246 $SIG{TERM} = 'DEFAULT';
247
248 $this->pm_remove_pid_file();
249
250 # prepare to die no matter what.
251 if (defined $this->die_timeout()) {
c2bbadb3 252 $SIG{ARLM} = sub { $this->pm_abort("wait timeout") };
518709ed 253 alarm $this->die_timeout();
254 }
255
256 # send a TERM to each of the servers.
257 kill "TERM", keys %{$this->{PIDS}};
258
259 # wait for the servers to die.
260 while (%{$this->{PIDS}}) {
c2bbadb3 261 $this->pm_wait();
518709ed 262 }
263
264 # die already.
265 $this->pm_exit("dying: ".$msg,$n);
0baf5fac 266}
267
c2bbadb3 268=head2 pm_wait
269
270 instance or export
271 (int pid) pm_wait()
272
273DESCRIPTION:
274
275This calls wait() which suspends execution until a child has exited.
276If the process ID returned by wait corresponds to a managed process,
277pm_notify() is called with the exit status of that process.
278pm_wait() returns with the return value of wait().
0baf5fac 279
280=cut
281
c2bbadb3 282sub pm_wait {
4ceac1a1 283 my ($this) = self_or_default(@_);
518709ed 284
285 # wait for the next server to die.
286 next if (my $pid = wait()) < 0;
287
288 # notify when one of our servers have died.
289 delete $this->{PIDS}->{$pid} and
290 $this->pm_notify("server (pid $pid) exited with status $?");
c2bbadb3 291
292 return $pid;
0baf5fac 293}
294
c146b63d 295=head2 pm_write_pid_file
0baf5fac 296
c2bbadb3 297 instance or export
298 () pm_write_pid_file([string filename])
299
300DESCRIPTION:
301
302Writes current process ID to optionally specified file. If no filename is
303specified, it uses the value of the C<pid_fname> parameter.
304
0baf5fac 305=cut
306
4ceac1a1 307sub pm_write_pid_file {
308 my ($this,$fname) = self_or_default(@_);
0baf5fac 309 $fname ||= $this->pid_fname() or return;
310 if (!open PIDFILE, ">$fname") {
518709ed 311 $this->pm_warn("open: $fname: $!");
0baf5fac 312 return;
313 }
314 print PIDFILE "$$\n";
315 close PIDFILE;
316}
317
c146b63d 318=head2 pm_remove_pid_file
0baf5fac 319
c2bbadb3 320 instance or export
321 () pm_remove_pid_file()
322
323DESCRIPTION:
324
325Removes optionally specified file. If no filename is specified, it uses
326the value of the C<pid_fname> parameter.
327
0baf5fac 328=cut
329
4ceac1a1 330sub pm_remove_pid_file {
331 my ($this,$fname) = self_or_default(@_);
0baf5fac 332 $fname ||= $this->pid_fname() or return;
518709ed 333 my $ret = unlink($fname) or $this->pm_warn("unlink: $fname: $!");
0baf5fac 334 return $ret;
335}
336
518709ed 337=head2 sig_manager
0baf5fac 338
c2bbadb3 339 instance
340 () sig_manager(string name)
341
342DESCRIPTION:
343
344Handles signals of the process manager. Takes as input the name of signal
345being handled.
346
0baf5fac 347=cut
348
518709ed 349sub sig_manager {
350 my ($this,$name) = @_;
351 if ($name eq "TERM" or $name eq "HUP") {
c2bbadb3 352 $this->pm_notify("received signal $name");
353 $this->pm_die("safe exit from signal $name");
518709ed 354 } else {
355 $this->pm_notify("ignoring signal $name");
356 }
0baf5fac 357}
358
518709ed 359=head1 Handler methods
0baf5fac 360
518709ed 361=head2 handling_init
0baf5fac 362
c2bbadb3 363 instance or export
364 () handling_init()
365
366DESCRIPTION:
367
0baf5fac 368=cut
369
518709ed 370sub handling_init {
c2bbadb3 371 my ($this) = @_;
0baf5fac 372
518709ed 373 # begin to handle signals.
374 $SIG{TERM} = sub { $this->sig_handler(@_) };
375 $SIG{HUP} = sub { $this->sig_handler(@_) };
0baf5fac 376
518709ed 377 # change the name of this process as it appears in ps(1) output.
378 $this->pm_change_process_name("perl-fcgi");
0baf5fac 379}
380
518709ed 381=head2 pm_pre_dispatch
0baf5fac 382
c2bbadb3 383 instance or export
384 () pm_pre_dispatch()
385
386DESCRIPTION:
387
0baf5fac 388=cut
389
518709ed 390sub pm_pre_dispatch {
4ceac1a1 391 my ($this) = self_or_default(@_);
0baf5fac 392}
393
518709ed 394=head2 pm_post_dispatch
0baf5fac 395
c2bbadb3 396 instance or export
397 () pm_post_dispatch()
398
399DESCRIPTION:
400
0baf5fac 401=cut
402
518709ed 403sub pm_post_dispatch {
4ceac1a1 404 my ($this) = self_or_default(@_);
518709ed 405 if ($this->pm_received_signal("TERM")) {
406 $this->pm_exit("safe exit after SIGTERM");
407 }
408 if ($this->pm_received_signal("HUP")) {
409 $this->pm_exit("safe exit after SIGHUP");
410 }
c2bbadb3 411 if ($this->{MANAGER_PID} and getppid() != $this->{MANAGER_PID}) {
518709ed 412 $this->pm_exit("safe exit: manager has died");
413 }
0baf5fac 414}
415
518709ed 416=head2 sig_handler
0baf5fac 417
c2bbadb3 418 instance or export
419 () sig_handler()
420
421DESCRIPTION:
422
0baf5fac 423=cut
424
518709ed 425sub sig_handler {
c146b63d 426 my ($this,$name) = @_;
518709ed 427 $this->pm_received_signal($name,1);
428}
429
430=head1 Common methods and routines
431
432=head2 self_or_default
433
434 private global
435 (ProcManager, @args) self_or_default([ ProcManager, ] @args);
436
437DESCRIPTION:
438
439This is a helper subroutine to acquire or otherwise create a singleton
440default object if one is not passed in, e.g., a method call.
441
442=cut
443
444sub self_or_default {
445 return @_ if defined $_[0] and !ref $_[0] and $_[0] eq 'FCGI::ProcManager';
446 if (!defined $_[0] or (ref($_[0]) ne 'FCGI::ProcManager' and
447 !UNIVERSAL::isa($_[0],'FCGI::ProcManager'))) {
448 $Q or $Q = $FCGI::ProcManager::Default->new;
449 unshift @_, $Q;
0baf5fac 450 }
518709ed 451 return wantarray ? @_ : $Q;
452}
453
454=head2 pm_change_process_name
455
c2bbadb3 456 instance or export
457 () pm_change_process_name()
458
459DESCRIPTION:
460
518709ed 461=cut
462
463sub pm_change_process_name {
464 my ($this,$name) = self_or_default(@_);
465 $0 = $name;
466}
467
468=head2 pm_received_signal
469
c2bbadb3 470 instance or export
471 () pm_received signal()
472
473DESCRIPTION:
474
518709ed 475=cut
476
477sub pm_received_signal {
478 my ($this,$sig,$received) = self_or_default(@_);
479 $sig or return $this->{SIG_RECEIVED};
480 $received and $this->{SIG_RECEIVED}->{$sig}++;
481 return $this->{SIG_RECEIVED}->{$sig};
482}
483
c2bbadb3 484=head1 parameters
485
518709ed 486=head2 pm_parameter
487
c2bbadb3 488 instance or export
489 () pm_parameter()
490
491DESCRIPTION:
492
518709ed 493=cut
494
495sub pm_parameter {
496 my ($this,$key,$value) = self_or_default(@_);
497 defined $value and $this->{$key} = $value;
498 return $this->{$key};
0baf5fac 499}
500
518709ed 501=head2 n_processes
502
503=head2 no_signals
504
505=head2 pid_fname
506
507=head2 die_timeout
508
509=head2 role
510
511=head2 start_delay
512
c2bbadb3 513DESCRIPTION:
514
518709ed 515=cut
516
517sub n_processes { shift->pm_parameter("n_processes", @_); }
518sub pid_fname { shift->pm_parameter("pid_fname", @_); }
519sub no_signals { shift->pm_parameter("no_signals", @_); }
520sub die_timeout { shift->pm_parameter("die_timeout", @_); }
521sub role { shift->pm_parameter("role", @_); }
522sub start_delay { shift->pm_parameter("start_delay", @_); }
523
c2bbadb3 524=head1 notification and death
525
c146b63d 526=head2 pm_warn
0baf5fac 527
c2bbadb3 528 instance or export
529 () pm_warn()
530
531DESCRIPTION:
532
0baf5fac 533=cut
534
4ceac1a1 535sub pm_warn {
536 my ($this,$msg) = self_or_default(@_);
518709ed 537 $this->pm_notify($msg);
538}
539
540=head2 pm_notify
541
c2bbadb3 542 instance or export
543 () pm_notify()
544
545DESCRIPTION:
546
518709ed 547=cut
548
549sub pm_notify {
550 my ($this,$msg) = self_or_default(@_);
551 $msg =~ s/\s*$/\n/;
552 print STDERR "FastCGI: ".$this->role()." (pid $$): ".$msg;
0baf5fac 553}
554
c2bbadb3 555=head2 pm_exit
556
557 instance or export
558 () pm_exit(string msg[, int exit_status])
559
560DESCRIPTION:
0baf5fac 561
562=cut
563
4ceac1a1 564sub pm_exit {
565 my ($this,$msg,$n) = self_or_default(@_);
0baf5fac 566 $n ||= 0;
c2bbadb3 567
568 # if we still have children at this point, something went wrong.
569 # SIGKILL them now.
570 kill "KILL", keys %{$this->{PIDS}} if $this->{PIDS};
571
4ceac1a1 572 $this->pm_warn($msg);
0baf5fac 573 $@ = $msg;
574 exit $n;
575}
576
c146b63d 577=head2 pm_abort
0baf5fac 578
c2bbadb3 579 instance or export
580 () pm_abort(string msg[, int exit_status])
581
582DESCRIPTION:
583
0baf5fac 584=cut
585
4ceac1a1 586sub pm_abort {
587 my ($this,$msg,$n) = self_or_default(@_);
0baf5fac 588 $n ||= 1;
4ceac1a1 589 $this->pm_exit($msg,1);
0baf5fac 590}
591
5921;
593__END__
594
595=head1 BUGS
596
597No known bugs, but this does not mean no bugs exist.
598
599=head1 SEE ALSO
600
601L<FCGI>.
602
603=head1 COPYRIGHT
604
605 FCGI-ProcManager - A Perl FCGI Process Manager
606 Copyright (c) 2000, FundsXpress Financial Network, Inc.
607
608 This library is free software; you can redistribute it and/or
609 modify it under the terms of the GNU Lesser General Public
610 License as published by the Free Software Foundation; either
611 version 2 of the License, or (at your option) any later version.
612
613 BECAUSE THIS LIBRARY IS LICENSED FREE OF CHARGE, THIS LIBRARY IS
614 BEING PROVIDED "AS IS WITH ALL FAULTS," WITHOUT ANY WARRANTIES
615 OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT
616 LIMITATION, ANY IMPLIED WARRANTIES OF TITLE, NONINFRINGEMENT,
617 MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, AND THE
618 ENTIRE RISK AS TO SATISFACTORY QUALITY, PERFORMANCE, ACCURACY,
619 AND EFFORT IS WITH THE YOU. See the GNU Lesser General Public
620 License for more details.
621
622 You should have received a copy of the GNU Lesser General Public
623 License along with this library; if not, write to the Free Software
624 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
625
626=cut