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