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