import FCGI-ProcManager 0.12 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
4ceac1a1 8# $Id: ProcManager.pm,v 1.10 2000/12/16 01:34:22 muaddib Exp $
0baf5fac 9
10use strict;
4ceac1a1 11use Exporter;
12use FCGI;
13
14use vars qw($VERSION @ISA @EXPORT_OK %EXPORT_TAGS $Q @valid_states);
0baf5fac 15BEGIN {
4ceac1a1 16 $VERSION = '0.12';
17 @ISA = qw(Exporter);
18 @EXPORT_OK = qw(pm_manage pm_parameter pm_state pm_warn pm_abort pm_exit
19 pm_write_pid_file pm_remove_pid_file
20 pm_register_sig_handler pm_unregister_sig_handler);
21 $EXPORT_TAGS{all} = \@EXPORT_OK;
22 $FCGI::ProcManager::Default = 'FCGI::ProcManager';
23
0baf5fac 24 @valid_states = qw(managing handling idle);
25}
26
27=head1 NAME
28
29 FCGI::ProcManager - functions for managing FastCGI applications.
30
31=head1 SYNOPSIS
32
33 # In its simplest form.
34 use CGI::Fast;
35 use FCGI::ProcManager;
36 my $proc_manager = FCGI::ProcManager->new({n_processes=>10});
37 $proc_manager->manage();
38 while (my $cgi = CGI::Fast->new()) {
39 #...
40
41=head1 DESCRIPTION
42
43FCGI::ProcManager is used to serve as a FastCGI process manager.
44The parent uses fork(2) and wait(2) to manage a set of FastCGI application
45servers. more later.
46
47=head1 METHODS
48
49=head2 new
50
51=cut
52
53sub new {
54 my ($proto,$init) = @_;
55
56 my $this = {};
0baf5fac 57 $init and %$this = %$init;
4ceac1a1 58
59 bless $this, ref($proto)||$proto;
0baf5fac 60
61 $this->{PIDS} = {};
62
63 return $this;
64}
65
4ceac1a1 66=head2 self_or_default
67
68 private global
69 (ProcManager, @args) self_or_default([ ProcManager, ] @args);
70
71DESCRIPTION:
72
73This is a helper subroutine to acquire or otherwise create a singleton
74default object if one is not passed in, e.g., a method call.
75
76=cut
77
78sub self_or_default {
79 return @_ if defined $_[0] and !ref $_[0] and $_[0] eq 'FCGI::ProcManager';
80 if (!defined $_[0] or (ref($_[0]) ne 'FCGI::ProcManager' or
81 !UNIVERSAL::isa($_[0],'FCGI::ProcManager'))) {
82 $Q or $Q = $FCGI::ProcManager::Default->new;
83 unshift @_, $Q;
84 }
85 return wantarray ? @_ : $Q;
86}
87
88=head2 pm_manage
0baf5fac 89
90 global
4ceac1a1 91 () pm_manage(int processes_to_spawn)
0baf5fac 92
93DESCRIPTION:
94
95When this is called by a FastCGI script to manage application servers.
96
97=cut
98
4ceac1a1 99sub pm_manage {
100 my ($this) = self_or_default(@_);
0baf5fac 101
102 # initialize state and begin to handle signals.
4ceac1a1 103 $this->pm_register_sig_handler();
0baf5fac 104
105 # return right away if we are not managing any processes.
106 $this->n_processes() or return 1;
107
108 # call the (possibly overloaded) pre-manage initialization.
4ceac1a1 109 $this->pm_state("managing");
0baf5fac 110 $this->pre_manage_init();
111
112 # write out the pid file.
113 $this->write_pid_file();
114
115 my ($pid);
116 MANAGE: while (1) {
117
118 # do things that we only do when we're not already managing processes..
119 if (! %{$this->{PIDS}}) {
120 if (!$this->n_processes()) {
121 $this->remove_pid_file();
122 last;
123 } elsif ($this->want_to_die()) {
124 $this->remove_pid_file();
4ceac1a1 125 $this->pm_exit("Manager $$ dying from death request.\n");
0baf5fac 126 } elsif ($this->n_processes() < 0) {
127 $this->remove_pid_file();
4ceac1a1 128 $this->pm_abort("Manager $$ dying from processes number exception: ".
129 $this->n_processes(), -( 1 + $this->n_processes()));
0baf5fac 130 }
131 }
132
133 # if we have fewer children than we want..
134 PIDS: while (keys(%{$this->{PIDS}}) < $this->n_processes()) {
135
136 # fork.
137 if ($pid = fork()) {
138 # the parent notes the child.
4ceac1a1 139 $this->pm_warn("started process $pid\n");
0baf5fac 140 $this->{PIDS}->{$pid} = { pid=>$pid };
141
142 } elsif (! defined $pid) {
143 # handle errors um gracefully.
4ceac1a1 144 $this->pm_abort("fork: $!\n");
0baf5fac 145
146 } else {
147 # the child returns to the calling application.
148 print "$$ lasting..\n";
149 last MANAGE;
150 }
151 }
152
153 # wait on the next child to die.
4ceac1a1 154 $this->pm_abort("wait: $!\n") if ($pid = wait()) < 0;
155 $this->pm_warn("Child process $pid died with exit status $?\n");
0baf5fac 156 delete $this->{PIDS}->{$pid}
4ceac1a1 157 or $this->pm_abort("Internal error: ".
158 "wait() returned non-existent pid=$pid??\n");
0baf5fac 159
160 }# while 1
161
162 # call the (possibly overloaded) post-manage initialization.
163 $this->post_manage_init();
164
4ceac1a1 165 $this->pm_state("idle");
0baf5fac 166
167 print "$$ returning..\n";
168 # children and parent with n_processes == 0 return to calling app.
169 return 1;
170}
171
172=head2 pre_manage_init
173
174=cut
175
176sub pre_manage_init {
4ceac1a1 177 my ($this) = self_or_default(@_);
0baf5fac 178}
179
180=head2 post_manage_init
181
182=cut
183
184sub post_manage_init {
4ceac1a1 185 my ($this) = self_or_default(@_);
0baf5fac 186}
187
188=head2 pre_dispatch
189
190=cut
191
192sub pre_dispatch {
4ceac1a1 193 my ($this) = self_or_default(@_);
194 $this->pm_state("handling");
0baf5fac 195}
196
197=head2 post_dispatch
198
199=cut
200
201sub post_dispatch {
4ceac1a1 202 my ($this) = self_or_default(@_);
0baf5fac 203 $this->want_to_die() and
4ceac1a1 204 $this->pm_exit("Process $$ responding to death request.");
205 $this->pm_state("idle");
0baf5fac 206}
207
208=head2 write_pid_file
209
210=cut
211
4ceac1a1 212sub pm_write_pid_file {
213 my ($this,$fname) = self_or_default(@_);
0baf5fac 214 $fname ||= $this->pid_fname() or return;
215 if (!open PIDFILE, ">$fname") {
4ceac1a1 216 $this->pm_warn("open: $fname: $!\n");
0baf5fac 217 return;
218 }
219 print PIDFILE "$$\n";
220 close PIDFILE;
221}
222
223=head2 remove_pid_file
224
225=cut
226
4ceac1a1 227sub pm_remove_pid_file {
228 my ($this,$fname) = self_or_default(@_);
0baf5fac 229 $fname ||= $this->pid_fname() or return;
4ceac1a1 230 my $ret = unlink($fname) or $this->pm_warn("unlink: $fname: $!\n");
0baf5fac 231 return $ret;
232}
233
4ceac1a1 234=head2 pm_parameter
0baf5fac 235
236=cut
237
4ceac1a1 238sub pm_parameter {
239 my ($this,$key,$value) = self_or_default(@_);
0baf5fac 240 defined $value and $this->{$key} = $value;
241 return $this->{$key};
242}
243
244=head2 n_processes
245
246=head2 want_to_die
247
248=head2 no_signals
249
250=head2 pid_fname
251
252=cut
253
4ceac1a1 254sub n_processes { shift->pm_parameter("n_processes",@_); }
255sub want_to_die { shift->pm_parameter("want_to_die",@_); }
256sub no_signals { shift->pm_parameter("no_signals",@_); }
257sub pid_fname { shift->pm_parameter("pid_fname",@_); }
0baf5fac 258
4ceac1a1 259=head2 pm_state
0baf5fac 260
261=cut
262
4ceac1a1 263sub pm_state {
264 my ($this,$new_state) = self_or_default(@_);
0baf5fac 265 if (defined $new_state) {
266 if (!grep {$new_state eq $_} @valid_states) {
4ceac1a1 267 $this->pm_abort("Invalid state: $new_state\n");
0baf5fac 268 }
269 $this->{state} = $new_state;
270 }
271 defined $this->{state} or $this->{state} = "idle";
272 return $this->{state};
273}
274
275=head2 register_sig_handler
276
277=cut
278
4ceac1a1 279sub pm_register_sig_handler {
280 my ($this) = self_or_default(@_);
0baf5fac 281 return if $this->no_signals();
282 $SIG{TERM} = sub { $this->sig_method(@_) };
283 $SIG{HUP} = sub { $this->sig_method(@_) };
284}
285
286=head2 unregister_sig_handler
287
288=cut
289
4ceac1a1 290sub pm_unregister_sig_handler {
291 my ($this) = self_or_default(@_);
0baf5fac 292 return if $this->no_signals();
293 undef $SIG{TERM};
294 undef $SIG{HUP};
295}
296
297=head2 sig_method
298
299=cut
300
301sub sig_method {
4ceac1a1 302 my ($this,$name) = self_or_default(@_);
303 if ($name eq "TERM" or $name eq "HUP") {
304 if ($this->pm_state() eq "idle") {
305 $this->pm_exit("Process $$ dying after receiving SIG$name.\n");
0baf5fac 306 } else {
4ceac1a1 307 $this->pm_warn("Process $$ received SIG$name. Cleaning up.\n");
0baf5fac 308 $this->want_to_die(1);
309 $this->n_processes(-1);
310 # is the following necessary?
311 kill $name, keys %{$this->{PIDS}};
312 }
313 } else {
4ceac1a1 314 $this->pm_warn("I don't know what to do with $name yet.. ignoring?\n");
0baf5fac 315 }
316}
317
318=head2 warn
319
320=cut
321
4ceac1a1 322sub pm_warn {
323 my ($this,$msg) = self_or_default(@_);
0baf5fac 324 print STDERR $msg;
325}
326
327=head2 exit
328
329=cut
330
4ceac1a1 331sub pm_exit {
332 my ($this,$msg,$n) = self_or_default(@_);
0baf5fac 333 $n ||= 0;
4ceac1a1 334 $this->pm_warn($msg);
0baf5fac 335 $@ = $msg;
336 exit $n;
337}
338
339=head2 abort
340
341=cut
342
4ceac1a1 343sub pm_abort {
344 my ($this,$msg,$n) = self_or_default(@_);
0baf5fac 345 $n ||= 1;
4ceac1a1 346 $this->pm_exit($msg,1);
0baf5fac 347}
348
3491;
350__END__
351
352=head1 BUGS
353
354No known bugs, but this does not mean no bugs exist.
355
356=head1 SEE ALSO
357
358L<FCGI>.
359
360=head1 COPYRIGHT
361
362 FCGI-ProcManager - A Perl FCGI Process Manager
363 Copyright (c) 2000, FundsXpress Financial Network, Inc.
364
365 This library is free software; you can redistribute it and/or
366 modify it under the terms of the GNU Lesser General Public
367 License as published by the Free Software Foundation; either
368 version 2 of the License, or (at your option) any later version.
369
370 BECAUSE THIS LIBRARY IS LICENSED FREE OF CHARGE, THIS LIBRARY IS
371 BEING PROVIDED "AS IS WITH ALL FAULTS," WITHOUT ANY WARRANTIES
372 OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING, WITHOUT
373 LIMITATION, ANY IMPLIED WARRANTIES OF TITLE, NONINFRINGEMENT,
374 MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE, AND THE
375 ENTIRE RISK AS TO SATISFACTORY QUALITY, PERFORMANCE, ACCURACY,
376 AND EFFORT IS WITH THE YOU. See the GNU Lesser General Public
377 License for more details.
378
379 You should have received a copy of the GNU Lesser General Public
380 License along with this library; if not, write to the Free Software
381 Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
382
383=cut